You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
14391 lines
484 KiB
14391 lines
484 KiB
|
|
<!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/5.15.4/css/all.min.css">
|
|
<style>
|
|
/* 基础容器样式 */
|
|
.needs-select-container {
|
|
position: relative;
|
|
width: 100%;
|
|
display: inline-block;
|
|
}
|
|
|
|
/* 下拉框触发按钮样式 */
|
|
.needs-select-trigger {
|
|
position: relative;
|
|
padding: 8px 12px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
align-items: center;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: right 10px center;
|
|
padding-right: 30px;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
min-height: 38px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 6px;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.needs-select-trigger .placeholder {
|
|
color: #999;
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.needs-select-trigger.active {
|
|
transform: scale(1.02);
|
|
box-shadow: 0 5px 15px rgba(232, 106, 146, 0.2);
|
|
border-color: rgba(232, 106, 146, 0.3);
|
|
}
|
|
|
|
/* 下拉菜单样式 */
|
|
.needs-select-dropdown {
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
margin-top: 5px;
|
|
padding: 5px 8px;
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
background-color: white;
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
|
|
z-index: 101;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transform: translateY(-10px) scale(0.95);
|
|
transform-origin: top center;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
overflow: hidden;
|
|
max-height: 300px;
|
|
overflow-y: auto;
|
|
scrollbar-width: thin;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.needs-select-dropdown::-webkit-scrollbar {
|
|
width: 6px;
|
|
}
|
|
|
|
.needs-select-dropdown::-webkit-scrollbar-thumb {
|
|
background-color: rgba(232, 106, 146, 0.3);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.needs-select-dropdown.active {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
transform: translateY(0) scale(1.02);
|
|
}
|
|
|
|
/* 选项样式 */
|
|
.needs-select-option {
|
|
padding: 10px 15px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
font-size: 14px;
|
|
position: relative;
|
|
border-radius: 6px;
|
|
margin: 0 2px;
|
|
display: flex;
|
|
align-items: center;
|
|
user-select: none;
|
|
}
|
|
|
|
.needs-select-option:hover {
|
|
background-color: rgba(232, 106, 146, 0.1);
|
|
transform: scale(1.03) translateX(3px);
|
|
}
|
|
|
|
.needs-select-option.selected {
|
|
background-color: rgba(232, 106, 146, 0.2);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.needs-select-option.selected::before {
|
|
content: "✓";
|
|
position: absolute;
|
|
right: 15px;
|
|
color: #E86A92;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.needs-select-option::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 15px;
|
|
width: calc(100% - 30px);
|
|
height: 2px;
|
|
background-color: #E86A92;
|
|
transform: scaleX(0);
|
|
transition: transform 0.3s ease;
|
|
transform-origin: left center;
|
|
}
|
|
|
|
.needs-select-option:hover::after {
|
|
transform: scaleX(0.4);
|
|
}
|
|
|
|
/* 全选选项样式 */
|
|
.needs-select-dropdown .select-all {
|
|
font-weight: 600;
|
|
color: #E86A92;
|
|
}
|
|
|
|
/* 分隔线样式 */
|
|
.needs-select-divider {
|
|
height: 1px;
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
margin: 5px 0;
|
|
}
|
|
|
|
/* 选中项标签样式 */
|
|
.selected-tag {
|
|
background-color: rgba(232, 106, 146, 0.15);
|
|
padding: 3px 8px;
|
|
border-radius: 20px;
|
|
font-size: 13px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
color: #E86A92;
|
|
transition: all 0.2s ease;
|
|
animation: tagIn 0.3s ease-out;
|
|
}
|
|
|
|
@keyframes tagIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.8) translateY(5px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1) translateY(0);
|
|
}
|
|
}
|
|
|
|
.selected-tag:hover {
|
|
background-color: rgba(232, 106, 146, 0.25);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.selected-tag .remove-tag {
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
color: #E86A92;
|
|
font-weight: bold;
|
|
line-height: 1;
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 50%;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.selected-tag .remove-tag:hover {
|
|
background-color: rgba(232, 106, 146, 0.3);
|
|
color: #d45a80;
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
/* 空状态提示 */
|
|
.empty-state {
|
|
color: #999;
|
|
text-align: center;
|
|
padding: 20px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* 最多选择提示 */
|
|
.selection-limit {
|
|
font-size: 12px;
|
|
color: #e74c3c;
|
|
padding: 5px 10px;
|
|
text-align: center;
|
|
animation: pulse 1.5s infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0% {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
50% {
|
|
opacity: 1;
|
|
}
|
|
|
|
100% {
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
|
|
/* 客户等级选择器动画样式 - 修改类名避免冲突 */
|
|
.level-select-container {
|
|
position: relative;
|
|
width: 100%;
|
|
z-index: 100;
|
|
}
|
|
|
|
.level-select-trigger {
|
|
position: relative;
|
|
padding: 8px 12px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: right 10px center;
|
|
padding-right: 30px;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
min-height: 38px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 6px;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.level-select-trigger .placeholder {
|
|
color: #999;
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.level-select-trigger.active {
|
|
transform: scale(1.02);
|
|
box-shadow: 0 5px 15px rgba(232, 106, 146, 0.2);
|
|
}
|
|
|
|
.level-select-dropdown {
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
margin-top: 5px;
|
|
padding: 5px 8px;
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
background-color: rgba(255, 255, 255, 0.99);
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
|
|
z-index: 99999;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transform: translateY(-10px) scale(0.95);
|
|
transform-origin: top center;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
overflow: hidden;
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
}
|
|
|
|
/* 客户等级选择器容器样式 - 确保整个选择器始终显示在最上层 */
|
|
.customer-level-container {
|
|
z-index: 200 !important;
|
|
}
|
|
|
|
/* 客户等级下拉框特定样式 - 确保始终显示在最上层 */
|
|
.customer-level-dropdown {
|
|
z-index: 999999 !important;
|
|
}
|
|
|
|
.level-select-dropdown.active {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
transform: translateY(0) scale(1.02);
|
|
}
|
|
|
|
.level-select-option {
|
|
padding: 10px 15px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
font-size: 14px;
|
|
position: relative;
|
|
border-radius: 6px;
|
|
margin: 0 2px;
|
|
user-select: none;
|
|
color: #333;
|
|
/* 所有选项默认颜色保持一致 */
|
|
}
|
|
|
|
.level-select-option:hover {
|
|
background-color: #f5f5f5;
|
|
/* 悬停背景保持统一 */
|
|
transform: scale(1.03) translateX(3px);
|
|
}
|
|
|
|
/* 边框缩短效果 - 使用伪元素实现 */
|
|
.level-select-option::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 15px;
|
|
width: calc(100% - 30px);
|
|
height: 2px;
|
|
background-color: #ccc;
|
|
/* 默认边框颜色 */
|
|
transform: scaleX(0);
|
|
transition: transform 0.3s ease;
|
|
transform-origin: left center;
|
|
}
|
|
|
|
.level-select-option:hover::after {
|
|
transform: scaleX(0.4);
|
|
}
|
|
|
|
.level-select-option.selected {
|
|
font-weight: 500;
|
|
position: relative;
|
|
}
|
|
|
|
.level-select-option.selected::before {
|
|
content: "✓";
|
|
position: absolute;
|
|
right: 15px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* 不同客户等级选择后的颜色设置 - 只在选中状态生效 */
|
|
/* 重要客户 - 绿色 */
|
|
.level-select-option[data-value="important"].selected {
|
|
color: #0e9245;
|
|
}
|
|
|
|
.level-select-option[data-value="important"].selected::before,
|
|
.level-select-option[data-value="important"].selected::after {
|
|
background-color: #27ae60;
|
|
color: #27ae60;
|
|
}
|
|
|
|
/* 客户类型选择后的颜色设置 */
|
|
/* 客户端 - 绿色 */
|
|
.level-select-option[data-value="客户端"].selected {
|
|
color: #27ae60;
|
|
background-color: rgba(39, 174, 96, 0.3);
|
|
}
|
|
|
|
.level-select-option[data-value="客户端"].selected::before,
|
|
.level-select-option[data-value="客户端"].selected::after {
|
|
background-color: #27ae60;
|
|
color: #27ae60;
|
|
}
|
|
|
|
/* 供应端 - 黄色 */
|
|
.level-select-option[data-value="供应端"].selected {
|
|
color: #f39c12;
|
|
background-color: rgba(243, 156, 18, 0.3);
|
|
}
|
|
|
|
.level-select-option[data-value="供应端"].selected::before,
|
|
.level-select-option[data-value="供应端"].selected::after {
|
|
background-color: #f39c12;
|
|
color: #f39c12;
|
|
}
|
|
|
|
/* BOTH - 红色 */
|
|
.level-select-option[data-value="BOTH"].selected {
|
|
color: #e74c3c;
|
|
background-color: rgba(231, 76, 60, 0.3);
|
|
}
|
|
|
|
.level-select-option[data-value="BOTH"].selected::before,
|
|
.level-select-option[data-value="BOTH"].selected::after {
|
|
background-color: #e74c3c;
|
|
color: #e74c3c;
|
|
}
|
|
|
|
/* 普通客户 - 蓝色 */
|
|
.level-select-option[data-value="normal"].selected {
|
|
color: #3498db;
|
|
}
|
|
|
|
.level-select-option[data-value="normal"].selected::before,
|
|
.level-select-option[data-value="normal"].selected::after {
|
|
background-color: #3498db;
|
|
color: #3498db;
|
|
}
|
|
|
|
/* 低价值客户 - 橙色 */
|
|
.level-select-option[data-value="low-value"].selected {
|
|
color: #e67e22;
|
|
}
|
|
|
|
.level-select-option[data-value="low-value"].selected::before,
|
|
.level-select-option[data-value="low-value"].selected::after {
|
|
background-color: #e67e22;
|
|
color: #e67e22;
|
|
}
|
|
|
|
/* 物流客户 - 紫色 */
|
|
.level-select-option[data-value="logistics"].selected {
|
|
color: #9b59b6;
|
|
}
|
|
|
|
.level-select-option[data-value="logistics"].selected::before,
|
|
.level-select-option[data-value="logistics"].selected::after {
|
|
background-color: #9b59b6;
|
|
color: #9b59b6;
|
|
}
|
|
|
|
/* 未分级客户 - 灰色 */
|
|
.level-select-option[data-value="unclassified"].selected {
|
|
color: #95a5a6;
|
|
}
|
|
|
|
.level-select-option[data-value="unclassified"].selected::before,
|
|
.level-select-option[data-value="unclassified"].selected::after {
|
|
background-color: #95a5a6;
|
|
color: #95a5a6;
|
|
}
|
|
|
|
/* 选中项显示样式 - 选择框中的文本样式 */
|
|
.selected-text {
|
|
font-size: 14px;
|
|
flex-grow: 1;
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* 选择框中不同等级的显示样式 */
|
|
.selected-text.important {
|
|
color: #27ae60;
|
|
background-color: rgba(39, 174, 96, 0.1);
|
|
}
|
|
|
|
/* 客户类型选择后填充整个选择框 */
|
|
/* 客户端 - 绿色 */
|
|
.level-select-trigger:has(.selected-text.客户端) {
|
|
background-color: rgba(39, 174, 96, 0.1);
|
|
color: #27ae60;
|
|
}
|
|
|
|
/* 供应端 - 黄色 */
|
|
.level-select-trigger:has(.selected-text.供应端) {
|
|
background-color: rgba(243, 156, 18, 0.1);
|
|
color: #f39c12;
|
|
}
|
|
|
|
/* BOTH - 红色 */
|
|
.level-select-trigger:has(.selected-text.BOTH) {
|
|
background-color: rgba(231, 76, 60, 0.1);
|
|
color: #e74c3c;
|
|
}
|
|
|
|
/* 客户等级选择后填充整个选择框 */
|
|
/* 重要客户 - 绿色 */
|
|
.level-select-trigger:has(.selected-text.important) {
|
|
background-color: rgba(39, 174, 96, 0.1);
|
|
color: #27ae60;
|
|
}
|
|
|
|
/* 普通客户 - 蓝色 */
|
|
.level-select-trigger:has(.selected-text.normal) {
|
|
background-color: rgba(52, 152, 219, 0.1);
|
|
color: #3498db;
|
|
}
|
|
|
|
/* 低价值客户 - 橙色 */
|
|
.level-select-trigger:has(.selected-text.low-value) {
|
|
background-color: rgba(230, 126, 34, 0.1);
|
|
color: #e67e22;
|
|
}
|
|
|
|
/* 物流客户 - 紫色 */
|
|
.level-select-trigger:has(.selected-text.logistics) {
|
|
background-color: rgba(155, 89, 182, 0.1);
|
|
color: #9b59b6;
|
|
}
|
|
|
|
/* 未分级客户 - 灰色 */
|
|
.level-select-trigger:has(.selected-text.unclassified) {
|
|
background-color: rgba(149, 165, 166, 0.1);
|
|
color: #95a5a6;
|
|
}
|
|
|
|
.selected-text.normal {
|
|
color: #3498db;
|
|
background-color: rgba(52, 152, 219, 0.1);
|
|
}
|
|
|
|
.selected-text.low-value {
|
|
color: #e67e22;
|
|
background-color: rgba(230, 126, 34, 0.1);
|
|
}
|
|
|
|
.selected-text.logistics {
|
|
color: #9b59b6;
|
|
background-color: rgba(155, 89, 182, 0.1);
|
|
}
|
|
|
|
.selected-text.unclassified {
|
|
color: #95a5a6;
|
|
background-color: rgba(149, 165, 166, 0.1);
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
}
|
|
|
|
body {
|
|
background: linear-gradient(135deg, #ebc4d2 0%, #c094ed 100%);
|
|
color: #333;
|
|
min-height: 100vh;
|
|
overflow-x: hidden;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
#particleCanvas {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: -1;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.container {
|
|
display: grid;
|
|
grid-template-columns: 300px 1fr;
|
|
gap: 20px;
|
|
padding: 20px;
|
|
max-width: 1800px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
}
|
|
|
|
.panel {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
backdrop-filter: blur(0.1s);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border-radius: 20px;
|
|
padding: 20px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
|
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
}
|
|
|
|
.header {
|
|
grid-column: 1 / -1;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 15px 25px;
|
|
}
|
|
|
|
/* 通知铃铛按钮样式 */
|
|
.notification-btn {
|
|
background-color: #f8f9fa;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 50%;
|
|
width: 40px;
|
|
height: 40px;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
margin-right: 10px;
|
|
color: #666;
|
|
position: relative;
|
|
}
|
|
|
|
.notification-btn:hover {
|
|
background-color: #e9ecef;
|
|
border-color: #adb5bd;
|
|
color: #495057;
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.notification-btn i {
|
|
font-size: 18px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
/* 通知铃铛激活状态样式 */
|
|
.notification-btn.notification-active {
|
|
background-color: #fee2e2;
|
|
border-color: #fecaca;
|
|
color: #ef4444;
|
|
}
|
|
|
|
/* 通知数量徽章样式 */
|
|
.notification-count {
|
|
position: absolute;
|
|
top: -5px;
|
|
right: -5px;
|
|
background: #ef4444;
|
|
color: white;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
border-radius: 50%;
|
|
min-width: 18px;
|
|
height: 18px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0 6px;
|
|
}
|
|
|
|
/* 铃铛摇晃动画 */
|
|
@keyframes ring {
|
|
0% { transform: rotate(0deg); }
|
|
10% { transform: rotate(15deg); }
|
|
20% { transform: rotate(-15deg); }
|
|
30% { transform: rotate(10deg); }
|
|
40% { transform: rotate(-10deg); }
|
|
50% { transform: rotate(5deg); }
|
|
60% { transform: rotate(-5deg); }
|
|
70% { transform: rotate(2deg); }
|
|
80% { transform: rotate(-2deg); }
|
|
90% { transform: rotate(1deg); }
|
|
100% { transform: rotate(0deg); }
|
|
}
|
|
|
|
/* 头部操作容器样式 */
|
|
.header-actions-container {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
/* 用户信息容器样式 */
|
|
.user-info-container {
|
|
position: relative;
|
|
}
|
|
|
|
/* 用户信息按钮样式 */
|
|
.user-info-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
background-color: #f8f9fa;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
padding: 8px 12px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
color: #666;
|
|
}
|
|
|
|
.user-info-btn:hover {
|
|
background-color: #e9ecef;
|
|
border-color: #adb5bd;
|
|
color: #495057;
|
|
}
|
|
|
|
.user-info-btn i {
|
|
margin: 0 5px;
|
|
}
|
|
|
|
/* 用户头像样式 */
|
|
.user-avatar-large {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
width: 24px;
|
|
height: 24px;
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
border-radius: 50%;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
/* 用户信息下拉菜单样式 */
|
|
.user-info-dropdown {
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 0;
|
|
background-color: white;
|
|
border: 1px solid #e0e0e0;
|
|
border-radius: 4px;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
padding: 10px 0;
|
|
min-width: 250px;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transition: all 0.3s ease;
|
|
transform: translateY(-5px);
|
|
}
|
|
|
|
.user-info-dropdown.active {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* 用户信息头部样式 */
|
|
.user-info-header {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 15px 10px;
|
|
border-bottom: 1px solid #e0e0e0;
|
|
}
|
|
|
|
/* 用户信息主要内容样式 */
|
|
.user-info-main {
|
|
margin-left: 10px;
|
|
}
|
|
|
|
/* 用户名字样式 */
|
|
.user-name {
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* 用户角色样式 */
|
|
.user-role {
|
|
font-size: 12px;
|
|
color: #666;
|
|
}
|
|
|
|
/* 用户信息详情样式 */
|
|
.user-info-details {
|
|
padding: 10px 0;
|
|
}
|
|
|
|
/* 用户信息项样式 */
|
|
.user-info-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 15px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.user-info-item:hover {
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
/* 用户信息标签样式 */
|
|
.user-info-label {
|
|
display: flex;
|
|
align-items: center;
|
|
color: #666;
|
|
}
|
|
|
|
.user-info-label i {
|
|
margin-right: 5px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* 用户信息值样式 */
|
|
.user-info-value {
|
|
color: #333;
|
|
font-weight: normal;
|
|
}
|
|
|
|
/* 退出按钮样式 */
|
|
.logout-btn {
|
|
width: 100%;
|
|
padding: 10px;
|
|
background-color: transparent;
|
|
border: none;
|
|
border-top: 1px solid #e0e0e0;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: #666;
|
|
}
|
|
|
|
.logout-btn:hover {
|
|
background-color: #f8f9fa;
|
|
color: #dc3545;
|
|
}
|
|
|
|
.logout-btn i {
|
|
margin-right: 5px;
|
|
}
|
|
|
|
.header-content {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.logo {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.current-time {
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
color: #333;
|
|
padding: 8px 15px;
|
|
border-radius: 8px;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
width: 225px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.welcome-message {
|
|
margin-left: 150px;
|
|
padding: 8px 15px;
|
|
border-radius: 8px;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
text-align: center;
|
|
animation: swing 3s ease-in-out infinite;
|
|
transform-origin: center;
|
|
}
|
|
|
|
@keyframes swing {
|
|
0% {
|
|
transform: translateY(0px);
|
|
}
|
|
|
|
25% {
|
|
transform: translateY(-5px);
|
|
}
|
|
|
|
75% {
|
|
transform: translateY(5px);
|
|
}
|
|
|
|
100% {
|
|
transform: translateY(0px);
|
|
}
|
|
}
|
|
|
|
.welcome-text {
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
background: linear-gradient(45deg, #e86a92, #8e44ad);
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
color: transparent;
|
|
}
|
|
|
|
.welcome-english {
|
|
font-size: 14px;
|
|
color: #666;
|
|
margin-top: 3px;
|
|
}
|
|
|
|
.sidebar {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
width: 280px;
|
|
min-width: 300px;
|
|
max-width: 300px;
|
|
flex-shrink: 0;
|
|
min-height: 600px;
|
|
height: 100%;
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
padding: 15px;
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
width: 100%;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
transform-origin: left center;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
background: rgba(0, 0, 0, 0.08);
|
|
transform: scale(1.03);
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: rgba(232, 106, 146, 0.2);
|
|
color: #e86a92;
|
|
font-weight: 500;
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.nav-item i {
|
|
font-size: 20px;
|
|
width: 24px;
|
|
text-align: center;
|
|
}
|
|
|
|
.main-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 25px;
|
|
height: 100%;
|
|
}
|
|
|
|
#customers-page .panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
min-height: 600px;
|
|
}
|
|
|
|
#customers-page .data-table-container {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
margin-top: 15px;
|
|
border-radius: 10px;
|
|
max-height: calc(100% - 200px);
|
|
}
|
|
|
|
#customers-page .search-and-filters {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.stats-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 20px;
|
|
}
|
|
|
|
.stat-card {
|
|
padding: 20px;
|
|
border-radius: 16px;
|
|
text-align: center;
|
|
background: rgba(255, 255, 255, 0.35);
|
|
transition: background 0.3s ease;
|
|
}
|
|
|
|
.stat-card:hover {
|
|
background: rgba(255, 255, 255, 0.45);
|
|
}
|
|
|
|
.stat-card i {
|
|
font-size: 28px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.stat-card:nth-child(1) i {
|
|
color: #e86a92;
|
|
}
|
|
|
|
.stat-card:nth-child(2) i {
|
|
color: #d68fb7;
|
|
}
|
|
|
|
.stat-card .value {
|
|
font-size: 28px;
|
|
font-weight: 700;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.stat-card .label {
|
|
font-size: 14px;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.customer-levels {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin-bottom: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.level-tab {
|
|
padding: 10px 20px;
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
font-weight: 500;
|
|
border: none;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
transform-origin: center;
|
|
}
|
|
|
|
.level-tab:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.level-tab.active {
|
|
background: linear-gradient(45deg, #e86a92, #8e44ad);
|
|
color: white;
|
|
transform: scale(1.1);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.level-tab.important {
|
|
border: 1px solid #27ae60;
|
|
}
|
|
|
|
.level-tab.important.active {
|
|
background: #27ae60;
|
|
}
|
|
|
|
.level-tab.normal {
|
|
border: 1px solid #3498db;
|
|
}
|
|
|
|
.level-tab.normal.active {
|
|
background: #3498db;
|
|
}
|
|
|
|
.level-tab.low {
|
|
border: 1px solid #f39c12;
|
|
}
|
|
|
|
.level-tab.low.active {
|
|
background: #f39c12;
|
|
}
|
|
|
|
.level-tab.all {
|
|
border: 1px solid #9b59b6;
|
|
}
|
|
|
|
.level-tab.all.active {
|
|
background: #9b59b6;
|
|
}
|
|
|
|
.level-tab.unclassified {
|
|
border: 1px solid #7f8c8d;
|
|
}
|
|
|
|
.level-tab.unclassified.active {
|
|
background: #7f8c8d;
|
|
}
|
|
|
|
.level-tab.public-sea {
|
|
border: 1px solid #34495e;
|
|
}
|
|
|
|
.level-tab.public-sea.active {
|
|
background: #34495e;
|
|
}
|
|
|
|
.data-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
|
|
.data-table th,
|
|
.data-table td {
|
|
padding: 15px;
|
|
text-align: left;
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.data-table th {
|
|
font-weight: 600;
|
|
opacity: 0.8;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
vertical-align: top;
|
|
white-space: pre-line;
|
|
}
|
|
|
|
/* 为最近客户表格的六个标题列平均分配宽度 */
|
|
#recent-customers-table th {
|
|
width: 16.67%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.data-table td.last-order-cell {
|
|
text-align: center;
|
|
vertical-align: middle;
|
|
padding: 15px;
|
|
}
|
|
|
|
.data-table tr:hover {
|
|
background: rgba(0, 0, 0, 0.05);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.action-btn {
|
|
padding: 8px 15px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
background: rgba(0, 0, 0, 0.1);
|
|
color: #333;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
background: rgba(0, 0, 0, 0.2);
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.search-bar {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.search-input {
|
|
flex: 1;
|
|
padding: 15px 20px;
|
|
border-radius: 12px;
|
|
border: none;
|
|
background: rgba(0, 0, 0, 0.08);
|
|
color: #333;
|
|
font-size: 16px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.search-input:focus {
|
|
outline: 1px solid rgba(0, 0, 0, 0.2);
|
|
transform: scale(1.01);
|
|
}
|
|
|
|
.primary-btn {
|
|
padding: 15px 25px;
|
|
border-radius: 12px;
|
|
border: none;
|
|
background: linear-gradient(45deg, #e86a92, #8e44ad);
|
|
color: white;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.primary-btn:hover {
|
|
transform: translateY(-3px);
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 1000;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.modal.active {
|
|
display: flex;
|
|
}
|
|
|
|
.modal-content {
|
|
background: white;
|
|
border-radius: 20px;
|
|
width: 90%;
|
|
max-width: 900px;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
padding: 0;
|
|
position: relative;
|
|
animation: fadeIn 0.3s ease;
|
|
}
|
|
|
|
/* 玻璃样式的标题栏 */
|
|
.modal-header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1001;
|
|
padding: 20px 30px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: rgba(255, 255, 255, 0.7);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.5);
|
|
border-radius: 20px 20px 0 0;
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.modal-title {
|
|
font-size: 24px;
|
|
margin: 0;
|
|
color: #333;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 30px;
|
|
}
|
|
|
|
.close-modal {
|
|
background: none;
|
|
border: none;
|
|
font-size: 24px;
|
|
cursor: pointer;
|
|
color: #666;
|
|
transition: transform 0.2s ease, color 0.2s ease;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.close-modal:hover {
|
|
transform: scale(1.2);
|
|
color: #e74c3c;
|
|
background: rgba(231, 76, 60, 0.1);
|
|
}
|
|
|
|
.customer-details {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 25px;
|
|
padding: 10px;
|
|
}
|
|
|
|
/* 新增:信息区域容器,用于平衡布局 */
|
|
.info-section-container {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 30px;
|
|
margin-top: -24px;
|
|
/* 向上移动约两个字的高度 */
|
|
}
|
|
|
|
.detail-section {
|
|
margin-bottom: 25px;
|
|
padding: 20px;
|
|
border-radius: 16px;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.6);
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.detail-section:hover {
|
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.detail-section-title {
|
|
font-size: 20px;
|
|
margin-bottom: 20px;
|
|
color: #4f46e5;
|
|
font-weight: 600;
|
|
padding-bottom: 12px;
|
|
border-bottom: 2px solid rgba(79, 70, 229, 0.2);
|
|
position: relative;
|
|
}
|
|
|
|
.detail-section-title::after {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: -2px;
|
|
left: 0;
|
|
width: 60px;
|
|
height: 2px;
|
|
background-color: #4f46e5;
|
|
border-radius: 1px;
|
|
}
|
|
|
|
.detail-item {
|
|
margin-bottom: 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.detail-label {
|
|
font-weight: 600;
|
|
color: #555;
|
|
min-width: 130px;
|
|
flex-shrink: 0;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.detail-value {
|
|
color: #333;
|
|
flex-grow: 1;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* 确保输入框与标签垂直居中对齐 */
|
|
.detail-input {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border-radius: 8px;
|
|
border: 1px solid #ddd;
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.address-container {
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.address-content {
|
|
display: inline-block;
|
|
max-width: 100%;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.customer-level {
|
|
padding: 5px 10px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.level-important {
|
|
background: rgba(39, 174, 96, 0.2);
|
|
color: #27ae60;
|
|
}
|
|
|
|
.level-normal {
|
|
background: rgba(52, 152, 219, 0.2);
|
|
color: #3498db;
|
|
}
|
|
|
|
.level-low {
|
|
background: rgba(243, 156, 18, 0.2);
|
|
color: #f39c12;
|
|
}
|
|
|
|
.level-unclassified {
|
|
background: rgba(127, 140, 141, 0.2);
|
|
color: #7f8c8d;
|
|
}
|
|
|
|
.level-public-sea {
|
|
background: rgba(52, 73, 94, 0.2);
|
|
color: #34495e;
|
|
}
|
|
|
|
.customer-type {
|
|
padding: 5px 10px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.type-supplier {
|
|
background: rgba(52, 152, 219, 0.2);
|
|
color: #3498db;
|
|
}
|
|
|
|
.type-customer {
|
|
background: rgba(232, 106, 146, 0.2);
|
|
color: #e86a92;
|
|
}
|
|
|
|
.status {
|
|
padding: 1px 3px;
|
|
border-radius: 20px;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status.active {
|
|
background: rgba(232, 106, 146, 0.2) !important;
|
|
color: #FF69B4 !important;
|
|
display: inline-block !important;
|
|
padding: 1px 3px !important;
|
|
margin: 0 !important;
|
|
text-align: center !important;
|
|
font-size: 16px !important;
|
|
font-weight: 500 !important;
|
|
}
|
|
|
|
.status-cell {
|
|
text-align: center;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.status-cell div:first-child {
|
|
margin-bottom: 4px;
|
|
color: #666;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.status.inactive {
|
|
background: rgba(231, 76, 60, 0.2);
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.content-section {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 22px;
|
|
font-weight: 600;
|
|
margin-bottom: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.page {
|
|
display: none;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.page.active {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 25px;
|
|
height: 100%;
|
|
animation: pageFadeIn 0.4s ease;
|
|
}
|
|
|
|
@keyframes pageFadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
margin-top: 20px;
|
|
gap: 10px;
|
|
padding: 10px 0;
|
|
}
|
|
|
|
.pagination-btn {
|
|
padding: 8px 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
background: rgba(255, 255, 255, 0.6);
|
|
color: #333;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.pagination-btn:hover:not(:disabled) {
|
|
background: rgba(255, 255, 255, 0.8);
|
|
transform: translateY(-2px) scale(1.03);
|
|
}
|
|
|
|
.pagination-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.pagination-btn.active {
|
|
background: linear-gradient(45deg, #e86a92, #8e44ad);
|
|
color: white;
|
|
border-color: transparent;
|
|
}
|
|
|
|
.pagination-info {
|
|
color: #666;
|
|
font-size: 14px;
|
|
margin: 0 10px;
|
|
}
|
|
|
|
.page-selector {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.page-input {
|
|
width: 50px;
|
|
padding: 6px;
|
|
border-radius: 6px;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
text-align: center;
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.page-input:focus {
|
|
outline: none;
|
|
border-color: rgba(232, 106, 146, 0.5);
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.page-go-btn {
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
background: rgba(255, 255, 255, 0.6);
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.page-go-btn:hover {
|
|
transform: scale(1.05);
|
|
background: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
.page-size-selector {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-right: 15px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.custom-select-container {
|
|
position: relative;
|
|
display: inline-block;
|
|
z-index: 101;
|
|
/* 确保下拉框在panel之上 */
|
|
}
|
|
|
|
.page-size-select {
|
|
padding: 6px 12px;
|
|
border-radius: 20px;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
background: rgba(255, 255, 255, 0.6);
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
appearance: none;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: right 10px center;
|
|
padding-right: 30px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.page-size-select:hover {
|
|
transform: scale(1.03);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
.page-size-select:focus {
|
|
outline: none;
|
|
border-color: rgba(232, 106, 146, 0.5);
|
|
box-shadow: 0 0 0 2px rgba(232, 106, 146, 0.2);
|
|
}
|
|
|
|
.custom-select-dropdown {
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
right: 0;
|
|
margin-top: 5px;
|
|
padding: 5px 0;
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
|
background-color: rgba(255, 255, 255, 0.95);
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
|
z-index: 100;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transform: translateY(-10px);
|
|
transition: all 0.3s ease-out;
|
|
}
|
|
|
|
.custom-select-dropdown.active {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.custom-select-option {
|
|
padding: 8px 15px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.custom-select-option:hover {
|
|
background-color: rgba(232, 106, 146, 0.1);
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.custom-select-option.selected {
|
|
background-color: rgba(232, 106, 146, 0.2);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.total-info {
|
|
margin-left: 15px;
|
|
color: #666;
|
|
font-size: 14px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
@media (max-width: 1100px) {
|
|
.customer-details {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.stats-cards {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.header-content {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.info-section-container {
|
|
grid-template-columns: 1fr;
|
|
margin-top: -24px;
|
|
/* 向上移动约两个字的高度 */
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
grid-template-columns: 1fr;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.sidebar {
|
|
order: 2;
|
|
width: 100%;
|
|
min-width: 100%;
|
|
max-width: 100%;
|
|
min-height: auto;
|
|
}
|
|
|
|
.stats-cards {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.header {
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.header-content {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
}
|
|
|
|
.welcome-message {
|
|
margin-left: 0;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.current-time {
|
|
width: auto;
|
|
}
|
|
|
|
.customer-levels {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.data-table {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.data-table th,
|
|
.data-table td {
|
|
padding: 10px 8px;
|
|
}
|
|
|
|
.pagination {
|
|
flex-wrap: wrap;
|
|
gap: 5px;
|
|
}
|
|
|
|
.pagination-btn {
|
|
padding: 6px 10px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.pagination-info {
|
|
width: 100%;
|
|
text-align: center;
|
|
margin: 5px 0;
|
|
}
|
|
|
|
.page-selector,
|
|
.page-size-selector {
|
|
width: 100%;
|
|
justify-content: center;
|
|
margin: 5px 0;
|
|
}
|
|
|
|
.total-info {
|
|
width: 100%;
|
|
text-align: center;
|
|
margin: 5px 0;
|
|
}
|
|
|
|
#customers-page .data-table-container {
|
|
max-height: calc(100% - 220px);
|
|
}
|
|
|
|
.detail-label {
|
|
min-width: 90px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
#customers-table th:nth-child(3),
|
|
#customers-table td:nth-child(3) {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 700px) {
|
|
|
|
#customers-table th:nth-child(2),
|
|
#customers-table td:nth-child(2) {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* 新增样式:添加详情按钮 */
|
|
.add-detail-btn {
|
|
background: rgba(232, 106, 146, 0.1);
|
|
color: #e86a92;
|
|
border: 1px solid #e86a92;
|
|
border-radius: 50%;
|
|
width: 24px;
|
|
height: 24px;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
/* 表格布局居中 */
|
|
display: table-cell;
|
|
vertical-align: middle;
|
|
text-align: center;
|
|
padding: 0;
|
|
margin-left: 8px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.add-detail-btn:hover {
|
|
background: #e86a92;
|
|
color: white;
|
|
transform: scale(1.2);
|
|
}
|
|
|
|
/* 新增样式:自定义详情区域 - 调整为与基本信息一致的排版 */
|
|
.custom-details-section {
|
|
padding-top: 15px;
|
|
border-top: 1px dashed #eee;
|
|
}
|
|
|
|
.detail-group {
|
|
margin-bottom: 20px;
|
|
padding-bottom: 15px;
|
|
border-bottom: 1px solid #f5f5f5;
|
|
}
|
|
|
|
.detail-group:last-child {
|
|
border-bottom: none;
|
|
margin-bottom: 0;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.detail-group-title {
|
|
font-weight: 500;
|
|
color: #e86a92;
|
|
margin-bottom: 10px;
|
|
font-size: 15px;
|
|
}
|
|
|
|
.detail-input {
|
|
width: 100%;
|
|
padding: 8px 12px;
|
|
border-radius: 8px;
|
|
border: 1px solid #ddd;
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.detail-input:focus {
|
|
outline: none;
|
|
border-color: #e86a92;
|
|
box-shadow: 0 0 0 2px rgba(232, 106, 146, 0.2);
|
|
}
|
|
|
|
.remove-detail-btn {
|
|
background: none;
|
|
border: none;
|
|
color: #e74c3c;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
padding: 5px 10px;
|
|
margin-top: 10px;
|
|
transition: all 0.2s ease;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
.remove-detail-btn:hover {
|
|
color: #c0392b;
|
|
transform: translateX(3px);
|
|
}
|
|
|
|
/* 新增详情弹窗样式 */
|
|
.add-detail-modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 1010;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.add-detail-modal.active {
|
|
display: flex;
|
|
}
|
|
|
|
.add-detail-modal-content {
|
|
background: white;
|
|
border-radius: 15px;
|
|
width: 90%;
|
|
max-width: 500px;
|
|
padding: 25px;
|
|
animation: fadeIn 0.3s ease;
|
|
}
|
|
|
|
.add-detail-title {
|
|
font-size: 20px;
|
|
margin-bottom: 20px;
|
|
color: #333;
|
|
}
|
|
|
|
.modal-input-group {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.modal-label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: 500;
|
|
color: #666;
|
|
}
|
|
|
|
.modal-input {
|
|
width: 100%;
|
|
padding: 10px 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid #ddd;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.modal-input:focus {
|
|
outline: none;
|
|
border-color: #e86a92;
|
|
box-shadow: 0 0 0 2px rgba(232, 106, 146, 0.2);
|
|
}
|
|
|
|
.modal-actions {
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
gap: 10px;
|
|
margin-top: 25px;
|
|
}
|
|
|
|
.modal-btn {
|
|
padding: 10px 20px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.modal-btn.cancel {
|
|
background: #eee;
|
|
color: #666;
|
|
}
|
|
|
|
.modal-btn.cancel:hover {
|
|
background: #ddd;
|
|
}
|
|
|
|
.modal-btn.confirm {
|
|
background: #e86a92;
|
|
color: white;
|
|
}
|
|
|
|
.modal-btn.confirm:hover {
|
|
background: #d45a80;
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* 跟进详情弹窗样式 */
|
|
.follow-up-detail-modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 1020;
|
|
align-items: center;
|
|
justify-content: center;
|
|
backdrop-filter: blur(5px);
|
|
-webkit-backdrop-filter: blur(5px);
|
|
}
|
|
|
|
.follow-up-detail-modal.active {
|
|
display: flex;
|
|
animation: fadeIn 0.3s ease;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
from {
|
|
transform: translateY(20px);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.follow-up-detail-content {
|
|
background: white;
|
|
border-radius: 16px;
|
|
width: 90%;
|
|
max-width: 800px;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
padding: 0;
|
|
position: relative;
|
|
animation: slideUp 0.3s ease;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.follow-up-detail-header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1021;
|
|
padding: 24px 32px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
border-bottom: none;
|
|
border-radius: 16px 16px 0 0;
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.follow-up-detail-title {
|
|
font-size: 24px;
|
|
margin: 0;
|
|
color: white;
|
|
font-weight: 600;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
}
|
|
|
|
.follow-up-detail-body {
|
|
padding: 32px;
|
|
}
|
|
|
|
.close-follow-up-detail {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
font-size: 24px;
|
|
cursor: pointer;
|
|
color: white;
|
|
transition: all 0.3s ease;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.close-follow-up-detail:hover {
|
|
transform: scale(1.1);
|
|
background: rgba(255, 255, 255, 0.3);
|
|
border-color: rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
/* 跟进详情表格样式 */
|
|
.follow-up-details-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.follow-up-details-table th {
|
|
text-align: left;
|
|
padding: 12px 0;
|
|
font-weight: 600;
|
|
color: #666;
|
|
font-size: 14px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.follow-up-details-table td {
|
|
padding: 12px 0;
|
|
}
|
|
|
|
/* 跟进信息输入区域样式 */
|
|
#follow-up-content {
|
|
width: 100%;
|
|
min-height: 160px;
|
|
padding: 16px;
|
|
border: 2px solid #e1e5e9;
|
|
border-radius: 12px;
|
|
font-size: 15px;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
line-height: 1.6;
|
|
resize: vertical;
|
|
transition: all 0.3s ease;
|
|
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
#follow-up-content:focus {
|
|
outline: none;
|
|
border-color: #667eea;
|
|
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
background-color: #f8faff;
|
|
}
|
|
|
|
#follow-up-content::placeholder {
|
|
color: #a0aec0;
|
|
font-style: italic;
|
|
}
|
|
|
|
/* 保存按钮样式 */
|
|
.save-follow-up-btn {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
padding: 14px 32px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.save-follow-up-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
|
background: linear-gradient(135deg, #5a6fd8 0%, #6a4091 100%);
|
|
}
|
|
|
|
.save-follow-up-btn:active {
|
|
transform: translateY(0);
|
|
box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
|
|
}
|
|
|
|
.save-follow-up-btn:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
transform: none;
|
|
box-shadow: none;
|
|
}
|
|
|
|
/* 跟进操作区域样式 */
|
|
.follow-up-actions {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin-top: 32px;
|
|
padding-top: 24px;
|
|
border-top: 1px solid #e1e5e9;
|
|
}
|
|
|
|
/* 详情部分样式 */
|
|
.detail-section {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.detail-section-title {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 20px;
|
|
padding-bottom: 12px;
|
|
border-bottom: 2px solid #f0f2f5;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
}
|
|
|
|
|
|
.follow-up-details-table th,
|
|
.follow-up-details-table td {
|
|
padding: 12px 15px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.follow-up-details-table th {
|
|
width: 30%;
|
|
font-weight: 600;
|
|
color: #666;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.follow-up-details-table tr:hover {
|
|
background: rgba(0, 0, 0, 0.02);
|
|
}
|
|
|
|
/* 跟进内容样式,提示可点击 */
|
|
.follow-up-content {
|
|
color: #e86a92;
|
|
text-decoration: underline;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.follow-up-content:hover {
|
|
color: #d45a80;
|
|
transform: translateX(3px);
|
|
}
|
|
|
|
.required {
|
|
color: red;
|
|
margin-left: 2px;
|
|
}
|
|
|
|
.detail-input:invalid {
|
|
border-color: red;
|
|
}
|
|
|
|
/* 只读字段样式 */
|
|
.detail-input[readonly] {
|
|
background-color: #f5f5f5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
|
|
/* 修改按钮样式 */
|
|
.edit-btn {
|
|
background-color: #007bff;
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 16px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
margin-right: 10px;
|
|
transition: background-color 0.3s;
|
|
}
|
|
|
|
.edit-btn.confirm-mode {
|
|
background-color: #28a745;
|
|
}
|
|
|
|
.edit-btn:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
/* 详情输入框样式 */
|
|
.detail-input,
|
|
.detail-select {
|
|
width: 100%;
|
|
padding: 4px 8px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.detail-input:focus,
|
|
.detail-select:focus {
|
|
outline: none;
|
|
border-color: #007bff;
|
|
box-shadow: 0 0 5px rgba(0, 123, 255, 0.3);
|
|
}
|
|
|
|
/* 确保模态框内容可点击 */
|
|
.modal-content {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.modal-content * {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
/* 添加刷新动画样式 */
|
|
@keyframes refreshSpin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.refreshing {
|
|
animation: refreshSpin 1s linear infinite;
|
|
}
|
|
|
|
/* 添加状态提示样式 */
|
|
.refresh-status {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: rgba(76, 175, 80, 0.9);
|
|
color: white;
|
|
padding: 10px 15px;
|
|
border-radius: 5px;
|
|
z-index: 10000;
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
display: none;
|
|
}
|
|
|
|
.refresh-status.show {
|
|
display: block;
|
|
animation: fadeInOut 2s ease-in-out;
|
|
}
|
|
|
|
@keyframes fadeInOut {
|
|
0% {
|
|
opacity: 0;
|
|
transform: translateY(-20px);
|
|
}
|
|
|
|
20% {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
80% {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
100% {
|
|
opacity: 0;
|
|
transform: translateY(-20px);
|
|
}
|
|
}
|
|
|
|
|
|
/*公司公海池样式*/
|
|
.level-tab.company-sea-pools {
|
|
border: 1px solid #e74c3c;
|
|
}
|
|
|
|
.level-tab.company-sea-pools.active {
|
|
background: #e74c3c;
|
|
}
|
|
|
|
.level-tab.organization-sea-pools {
|
|
border: 1px solid #9b59b6;
|
|
}
|
|
|
|
.level-tab.organization-sea-pools.active {
|
|
background: #9b59b6;
|
|
}
|
|
|
|
.level-tab.department-sea-pools {
|
|
border: 1px solid #34495e;
|
|
}
|
|
|
|
.level-tab.department-sea-pools.active {
|
|
background: #34495e;
|
|
}
|
|
|
|
|
|
/* 添加表格行点击效果 */
|
|
#customers-table tbody tr,
|
|
#recent-customers-table tbody tr {
|
|
cursor: pointer;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
#customers-table tbody tr:hover,
|
|
#recent-customers-table tbody tr:hover {
|
|
background-color: rgba(232, 106, 146, 0.1) !important;
|
|
}
|
|
|
|
/* 操作按钮区域样式 */
|
|
.action-btn {
|
|
pointer-events: auto !important;
|
|
z-index: 10;
|
|
position: relative;
|
|
}
|
|
|
|
/* 确保操作按钮可点击且不影响行点击 */
|
|
.action-cell {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
/* 公海池客户特殊样式 */
|
|
.public-sea-customer {
|
|
border-left: 3px solid #e74c3c;
|
|
}
|
|
|
|
|
|
/* 公海需求卡片样式 */
|
|
.demand-card {
|
|
border: 2px solid #e0e0e0;
|
|
border-radius: 10px;
|
|
padding: 15px;
|
|
width: 280px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
background: white;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.demand-card:hover {
|
|
border-color: #e86a92;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 15px rgba(232, 106, 146, 0.2);
|
|
}
|
|
|
|
.demand-card.selected {
|
|
border-color: #e86a92;
|
|
background: rgba(232, 106, 146, 0.05);
|
|
}
|
|
|
|
.demand-card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 10px;
|
|
padding-bottom: 8px;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
}
|
|
|
|
.demand-card-title {
|
|
font-weight: 600;
|
|
color: #e86a92;
|
|
font-size: 16px;
|
|
}
|
|
|
|
.demand-card-badge {
|
|
background: #e86a92;
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.demand-card-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.demand-card-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.demand-card-label {
|
|
color: #666;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.demand-card-value {
|
|
color: #333;
|
|
text-align: right;
|
|
flex: 1;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
|
|
/* 时间筛选器样式 */
|
|
.time-filter-container {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
margin-bottom: 20px;
|
|
padding: 15px;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
border-radius: 12px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.filter-label {
|
|
font-weight: 500;
|
|
color: #666;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.cascader-container {
|
|
position: relative;
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.cascader-select {
|
|
padding: 10px 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid #ddd;
|
|
background: white;
|
|
cursor: pointer;
|
|
min-width: 150px;
|
|
position: relative;
|
|
}
|
|
|
|
.cascader-select::after {
|
|
content: "▼";
|
|
position: absolute;
|
|
right: 10px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
font-size: 12px;
|
|
color: #666;
|
|
}
|
|
|
|
.cascader-dropdown {
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
margin-top: 5px;
|
|
padding: 10px;
|
|
border-radius: 8px;
|
|
border: 1px solid #ddd;
|
|
background: white;
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
|
z-index: 1000;
|
|
min-width: 200px;
|
|
display: none;
|
|
}
|
|
|
|
.cascader-dropdown.active {
|
|
display: block;
|
|
}
|
|
|
|
.cascader-option {
|
|
padding: 8px 12px;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.cascader-option:hover {
|
|
background: rgba(232, 106, 146, 0.1);
|
|
}
|
|
|
|
.date-inputs {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
|
|
.date-input {
|
|
padding: 10px 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid #ddd;
|
|
background: white;
|
|
}
|
|
|
|
.apply-filter-btn {
|
|
padding: 10px 20px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
background: rgba(232, 106, 146, 0.8);
|
|
color: white;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.apply-filter-btn:hover {
|
|
background: rgba(232, 106, 146, 1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.reset-filter-btn {
|
|
padding: 10px 20px;
|
|
border-radius: 8px;
|
|
border: none;
|
|
background: rgba(108, 117, 125, 0.8);
|
|
color: white;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
margin-left: 10px;
|
|
}
|
|
|
|
.reset-filter-btn:hover {
|
|
background: rgba(108, 117, 125, 1);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* 用户信息下拉菜单样式 - 玻璃态设计 */
|
|
.user-info-container {
|
|
position: relative;
|
|
display: inline-block;
|
|
}
|
|
|
|
.user-info-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 10px 16px;
|
|
background: rgba(255, 255, 255, 0.25);
|
|
backdrop-filter: blur(10px);
|
|
-webkit-backdrop-filter: blur(10px);
|
|
color: #333;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
min-width: 140px;
|
|
justify-content: space-between;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.user-info-btn:hover {
|
|
background: rgba(255, 255, 255, 0.35);
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
|
border-color: rgba(232, 106, 146, 0.3);
|
|
}
|
|
|
|
.user-info-btn:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.user-info-dropdown {
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 0;
|
|
margin-top: 8px;
|
|
background: rgba(255, 255, 255, 0.9);
|
|
backdrop-filter: blur(15px);
|
|
-webkit-backdrop-filter: blur(15px);
|
|
border-radius: 16px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
|
padding: 20px;
|
|
min-width: 280px;
|
|
z-index: 1000;
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transform: translateY(-10px) scale(0.95);
|
|
transform-origin: top right;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
border: 1px solid rgba(255, 255, 255, 0.4);
|
|
}
|
|
|
|
.user-info-dropdown.active {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
|
|
.user-info-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
padding-bottom: 12px;
|
|
border-bottom: 1px solid rgba(232, 106, 146, 0.2);
|
|
}
|
|
|
|
.user-avatar-large {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, #e86a92, #8e44ad);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
color: white;
|
|
box-shadow: 0 4px 12px rgba(232, 106, 146, 0.3);
|
|
}
|
|
|
|
.user-info-main {
|
|
flex: 1;
|
|
}
|
|
|
|
.user-name {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.user-role {
|
|
font-size: 12px;
|
|
color: #e86a92;
|
|
background: rgba(232, 106, 146, 0.1);
|
|
padding: 2px 8px;
|
|
border-radius: 12px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.user-info-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.user-info-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 0;
|
|
}
|
|
|
|
.user-info-item:not(:last-child) {
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.user-info-label {
|
|
font-weight: 500;
|
|
color: #666;
|
|
font-size: 13px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.user-info-label i {
|
|
width: 16px;
|
|
text-align: center;
|
|
color: #e86a92;
|
|
}
|
|
|
|
.user-info-value {
|
|
color: #333;
|
|
font-size: 13px;
|
|
text-align: right;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.logout-btn {
|
|
width: 100%;
|
|
padding: 12px;
|
|
background: linear-gradient(135deg, #e86a92, #8e44ad);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
box-shadow: 0 4px 12px rgba(232, 106, 146, 0.3);
|
|
}
|
|
|
|
.logout-btn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 20px rgba(232, 106, 146, 0.4);
|
|
background: linear-gradient(135deg, #d45a80, #7d3c98);
|
|
}
|
|
|
|
.logout-btn:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* 用户信息按钮中的图标动画 */
|
|
.user-info-btn .fa-chevron-down {
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.user-info-btn.active .fa-chevron-down {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
|
|
/* 负责人信息字段样式 */
|
|
.manager-info-field {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.manager-info-field[readonly] {
|
|
background-color: #f5f5f5;
|
|
color: #666;
|
|
cursor: not-allowed;
|
|
border-color: #e0e0e0;
|
|
}
|
|
|
|
.manager-info-field:not([readonly]) {
|
|
background-color: #fff;
|
|
color: #333;
|
|
cursor: text;
|
|
border-color: #ddd;
|
|
}
|
|
|
|
/* 协助人字段特殊样式 */
|
|
.assistant-field {
|
|
background-color: #fff !important;
|
|
border: 1px solid #4CAF50 !important;
|
|
box-shadow: 0 0 5px rgba(76, 175, 80, 0.3);
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
<canvas id="particleCanvas"></canvas>
|
|
|
|
<div class="container">
|
|
<div class="header panel">
|
|
<div class="header-content">
|
|
<div class="logo">
|
|
<i class="fas fa-users"></i>
|
|
<span>供应关系管理</span>
|
|
</div>
|
|
<div class="current-time" id="beijingTime">加载中...</div>
|
|
<div class="welcome-message">
|
|
<div class="welcome-text">欢迎,祝您有个愉快的一天</div>
|
|
<div class="welcome-english">Welcome, hope you have a nice day</div>
|
|
</div>
|
|
</div>
|
|
<button class="notification-btn" id="notificationButton">
|
|
<i class="fas fa-bell"></i>
|
|
</button>
|
|
|
|
<!-- 通知弹窗 -->
|
|
<div class="modal" id="notificationModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">客户通知</h2>
|
|
<button class="close-modal" id="closeNotificationModal">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="notificationContent">
|
|
<!-- 通知内容将通过JavaScript动态生成 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<button class="primary-btn login-btn" id="loginButton">
|
|
<i class="fas fa-sign-in-alt"></i> 登录
|
|
</button>
|
|
</div>
|
|
|
|
<div class="sidebar panel">
|
|
<div class="nav-item active" data-page="dashboard">
|
|
<i class="fas fa-home"></i>
|
|
<span>控制面板</span>
|
|
</div>
|
|
<div class="nav-item" data-page="customers">
|
|
<i class="fas fa-user-friends"></i>
|
|
<span>客户管理</span>
|
|
</div>
|
|
<!-- 新增:跟进导航项 -->
|
|
<div class="nav-item" data-page="follow-up">
|
|
<i class="fas fa-tasks"></i>
|
|
<span>跟进</span>
|
|
</div>
|
|
<div class="nav-item" data-page="analytics">
|
|
<i class="fas fa-chart-line"></i>
|
|
<span>数据分析</span>
|
|
</div>
|
|
<div class="nav-item" data-page="schedule">
|
|
<i class="fas fa-calendar-alt"></i>
|
|
<span>日程安排</span>
|
|
</div>
|
|
<div class="nav-item" data-page="settings">
|
|
<i class="fas fa-cog"></i>
|
|
<span>系统设置</span>
|
|
</div>
|
|
<div class="nav-item">
|
|
<i class="fas fa-sign-out-alt"></i>
|
|
<span>退出登录</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 控制面板页面 -->
|
|
<div class="page active" id="dashboard-page">
|
|
<div class="panel">
|
|
<div class="search-bar">
|
|
<input type="text" class="search-input" id="dashboard-search" placeholder="搜索客户...">
|
|
<button class="primary-btn" id="dashboard-add-customer">
|
|
<i class="fas fa-plus"></i> 新增客户
|
|
</button>
|
|
</div>
|
|
|
|
<div class="stats-cards">
|
|
<div class="stat-card">
|
|
<i class="fas fa-users"></i>
|
|
<div class="value">0</div>
|
|
<div class="label">总客户数</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<i class="fas fa-user-check"></i>
|
|
<div class="value">0</div>
|
|
<div class="label">活跃客户</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<i class="fas fa-chart-pie" style="color: #8e44ad;"></i>
|
|
<div class="value">0%</div>
|
|
<div class="label">客户留存率</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<i class="fas fa-money-bill-wave" style="color: #e74c3c;"></i>
|
|
<div class="value">¥0</div>
|
|
<div class="label">本月销售额</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="panel">
|
|
<div class="content-section">
|
|
<h2 class="section-title">
|
|
<i class="fas fa-list"></i>
|
|
最近客户
|
|
</h2>
|
|
|
|
<table class="data-table" id="recent-customers-table">
|
|
<thead>
|
|
<tr>
|
|
<th>客户名称</th>
|
|
<th>联系人</th>
|
|
<th>联系方式</th>
|
|
<th>最后订单</th>
|
|
<th>状态</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- 清空最近客户数据 -->
|
|
<tr>
|
|
<td colspan="6" style="text-align: center; color: #666;">暂无客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 客户管理页面 -->
|
|
<div class="page" id="customers-page">
|
|
<div class="panel">
|
|
<div class="search-and-filters">
|
|
<div class="search-bar">
|
|
<input type="text" class="search-input" id="customers-search" placeholder="搜索客户...">
|
|
<button class="primary-btn" id="customers-add-customer">
|
|
<i class="fas fa-plus"></i> 新增客户
|
|
</button>
|
|
</div>
|
|
|
|
<div class="customer-levels">
|
|
<button class="level-tab all" data-level="all">全部客户</button>
|
|
<button class="level-tab important active" data-level="important">重要客户</button>
|
|
<button class="level-tab normal" data-level="normal">普通客户</button>
|
|
<button class="level-tab low" data-level="low">低价值客户</button>
|
|
<button class="level-tab logistics" data-level="logistics">物流客户</button>
|
|
<button class="level-tab unclassified" data-level="unclassified">未分级客户</button>
|
|
<button class="level-tab company-sea-pools" data-level="company-sea-pools">公司公海池</button>
|
|
<button class="level-tab organization-sea-pools" data-level="organization-sea-pools">组织公海池</button>
|
|
<button class="level-tab department-sea-pools" data-level="department-sea-pools">部门公海池</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 时间筛选器部分 -->
|
|
<div class="time-filter-container">
|
|
<div class="filter-label">时间筛选:</div>
|
|
<div class="cascader-container">
|
|
<div class="cascader-select" id="filter-type-select">
|
|
<span id="filter-type-display">动态筛选</span>
|
|
<div class="cascader-dropdown" id="filter-type-dropdown">
|
|
<div class="cascader-option" data-value="dynamic">动态筛选</div>
|
|
<div class="cascader-option" data-value="custom">自定义筛选</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="cascader-select" id="dynamic-options-select">
|
|
<span id="dynamic-option-display">今天</span>
|
|
<div class="cascader-dropdown" id="dynamic-options-dropdown">
|
|
<div class="cascader-option" data-value="today">今天</div>
|
|
<div class="cascader-option" data-value="yesterday">昨天</div>
|
|
<div class="cascader-option" data-value="dayBeforeYesterday">前天</div>
|
|
<div class="cascader-option" data-value="threeDaysAgo">大前天</div>
|
|
<div class="cascader-option" data-value="lastWeek">一周内</div>
|
|
<div class="cascader-option" data-value="lastMonth">一个月内</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="date-inputs" id="custom-date-inputs" style="display: none;">
|
|
<input type="datetime-local" class="date-input" id="start-date">
|
|
<span>至</span>
|
|
<input type="datetime-local" class="date-input" id="end-date">
|
|
</div>
|
|
|
|
<button class="apply-filter-btn" id="apply-filter">应用筛选</button>
|
|
<button class="reset-filter-btn" id="reset-filter">重置</button>
|
|
<button class="reset-filter-btn" id="refresh-data">刷新</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="data-table-container">
|
|
<table class="data-table" id="customers-table">
|
|
<thead>
|
|
<tr>
|
|
<th>客户公司</th>
|
|
<th>客户地区</th>
|
|
<th>基本需求</th>
|
|
<th>规格</th>
|
|
<th>联系人</th>
|
|
<th>电话</th>
|
|
<th>创建时间</th>
|
|
<th>修改时间</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- 清空所有客户数据 -->
|
|
<tbody id="important-customers">
|
|
<tr>
|
|
<td colspan="7" style="text-align: center; color: #666;">暂无重要客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
<tbody id="normal-customers" style="display: none;">
|
|
<tr>
|
|
<td colspan="7" style="text-align: center; color: #666;">暂无普通客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
<tbody id="low-customers" style="display: none;">
|
|
<tr>
|
|
<td colspan="7" style="text-align: center; color: #666;">暂无低价值客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
<tbody id="logistics-customers" style="display: none;">
|
|
<tr>
|
|
<td colspan="7" style="text-align: center; color: #666;">暂无物流客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
<tbody id="unclassified-customers" style="display: none;">
|
|
<tr>
|
|
<td colspan="7" style="text-align: center; color: #666;">暂无未分级客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
<tbody id="company-sea-pools-customers" style="display: none;">
|
|
<tr>
|
|
<td colspan="9" style="text-align: center; color: #666;">暂无公司公海池客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
<tbody id="organization-sea-pools-customers" style="display: none;">
|
|
<tr>
|
|
<td colspan="9" style="text-align: center; color: #666;">暂无组织公海池客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
|
|
<tbody id="department-sea-pools-customers" style="display: none;">
|
|
<tr>
|
|
<td colspan="9" style="text-align: center; color: #666;">暂无部门公海池客户数据</td>
|
|
</tr>
|
|
</tbody>
|
|
<!-- 新增:全部客户表格 -->
|
|
<tbody id="all-customers" style="display: none;"></tbody>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="pagination" id="all-customers-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="all-customers-prev-page" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="all-customers-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="all-customers-next-page" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="all-customers-page-input" min="1" value="1">
|
|
<button class="page-go-btn" id="all-customers-page-go-btn">确定</button>
|
|
</div>
|
|
|
|
<span class="total-info" id="all-customers-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
<!-- 重要客户分页 -->
|
|
<div class="pagination" id="important-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="prev-page-important" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="important-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="next-page-important" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="page-input-important" min="1" value="1">
|
|
<button class="page-go-btn" id="page-go-btn-important">确定</button>
|
|
</div>
|
|
|
|
<span class="total-info" id="important-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
|
|
<!-- 普通客户分页 -->
|
|
<div class="pagination" id="normal-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="prev-page-normal" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="normal-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="next-page-normal" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="page-input-normal" min="1" value="1">
|
|
<button class="page-go-btn" id="page-go-btn-normal">确定</button>
|
|
</div>
|
|
|
|
<span class="total-info" id="normal-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
|
|
<!-- 低价值客户分页 -->
|
|
<div class="pagination" id="low-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="prev-page-low" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="low-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="next-page-low" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="page-input-low" min="1" value="1">
|
|
<button class="page-go-btn" id="page-go-btn-low">确定</button>
|
|
</div>
|
|
|
|
<span class="total-info" id="low-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
|
|
<!-- 物流客户分页 -->
|
|
<div class="pagination" id="logistics-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="prev-page-logistics" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="logistics-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="next-page-logistics" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="page-input-logistics" min="1" value="1">
|
|
<button class="page-go-btn" id="page-go-btn-logistics">确定</button>
|
|
</div>
|
|
|
|
<span class="total-info" id="logistics-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
|
|
<!-- 未分级客户分页 -->
|
|
<div class="pagination" id="unclassified-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="prev-page-unclassified" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="unclassified-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="next-page-unclassified" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="page-input-unclassified" min="1" value="1">
|
|
<button class="page-go-btn" id="page-go-btn-unclassified">确定</button>
|
|
</div>
|
|
|
|
<span class="total-info" id="unclassified-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
|
|
<!-- 公司公海池分页 -->
|
|
<div class="pagination" id="company-sea-pools-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="prev-page-company-sea-pools" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="company-sea-pools-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="next-page-company-sea-pools" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="page-input-company-sea-pools" min="1" value="1">
|
|
<button class="page-go-btn" id="page-go-btn-company-sea-pools">确定</button>
|
|
</div>
|
|
<span class="total-info" id="company-sea-pools-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
|
|
<!-- 组织公海池分页 -->
|
|
<div class="pagination" id="organization-sea-pools-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="prev-page-organization-sea-pools" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="organization-sea-pools-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="next-page-organization-sea-pools" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="page-input-organization-sea-pools" min="1" value="1">
|
|
<button class="page-go-btn" id="page-go-btn-organization-sea-pools">确定</button>
|
|
</div>
|
|
<span class="total-info" id="organization-sea-pools-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
|
|
<!-- 部门公海池分页 -->
|
|
<div class="pagination" id="department-sea-pools-pagination" style="display: none;">
|
|
<button class="pagination-btn" id="prev-page-department-sea-pools" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="department-sea-pools-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="next-page-department-sea-pools" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="page-input-department-sea-pools" min="1" value="1">
|
|
<button class="page-go-btn" id="page-go-btn-department-sea-pools">确定</button>
|
|
</div>
|
|
<span class="total-info" id="department-sea-pools-total-info">显示 1-10 条 / 总计: 0 条</span>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 新增:跟进页面 -->
|
|
<div class="page" id="follow-up-page">
|
|
<div class="panel">
|
|
<h2 class="section-title"><i class="fas fa-tasks"></i>客户跟进管理</h2>
|
|
|
|
<div class="search-bar">
|
|
<input type="text" class="search-input" id="follow-up-search" placeholder="搜索跟进记录...">
|
|
<button class="primary-btn">
|
|
<i class="fas fa-plus"></i> 新增跟进
|
|
</button>
|
|
</div>
|
|
|
|
<div class="data-table-container">
|
|
<table class="data-table" id="follow-up-table">
|
|
<thead>
|
|
<tr>
|
|
<th>跟进详情</th>
|
|
<th>联系人</th>
|
|
<th>电话</th>
|
|
<th>日期</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- 清空跟进记录数据 -->
|
|
<tr>
|
|
<td colspan="5" style="text-align: center; color: #666;">暂无跟进记录</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="pagination" id="follow-up-pagination">
|
|
<div class="page-size-selector">
|
|
<span>每页显示:</span>
|
|
<div class="custom-select-container">
|
|
<div class="page-size-select" id="follow-up-page-size-display">50条</div>
|
|
<div class="custom-select-dropdown" id="follow-up-page-size-dropdown">
|
|
<div class="custom-select-option selected" data-value="50">50条</div>
|
|
<div class="custom-select-option" data-value="100">100条</div>
|
|
<div class="custom-select-option" data-value="all">全部</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="pagination-btn" id="follow-up-prev-page" disabled>
|
|
<i class="fas fa-chevron-left"></i> 上一页
|
|
</button>
|
|
<span class="pagination-info" id="follow-up-page-info">第 1 页,共 1 页</span>
|
|
<button class="pagination-btn" id="follow-up-next-page" disabled>
|
|
下一页 <i class="fas fa-chevron-right"></i>
|
|
</button>
|
|
|
|
<div class="page-selector">
|
|
<input type="number" class="page-input" id="follow-up-page-input" min="1" value="1">
|
|
<button class="page-go-btn" id="follow-up-page-go-btn">确定</button>
|
|
</div>
|
|
|
|
<span class="total-info" id="follow-up-total-info">当前页面: 0条 / 总计: 0条</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 其他页面占位 -->
|
|
<div class="page" id="analytics-page">
|
|
<div class="panel">
|
|
<h2 class="section-title"><i class="fas fa-chart-line"></i>数据分析</h2>
|
|
<p>数据分析功能即将上线,敬请期待。</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="page" id="schedule-page">
|
|
<div class="panel">
|
|
<h2 class="section-title"><i class="fas fa-calendar-alt"></i>日程安排</h2>
|
|
<p>日程安排功能即将上线,敬请期待。</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="page" id="settings-page">
|
|
<div class="panel">
|
|
<h2 class="section-title"><i class="fas fa-cog"></i>系统设置</h2>
|
|
<p>系统设置功能即将上线,敬请期待。</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 客户详情弹窗 -->
|
|
<div class="modal" id="customerModal">
|
|
<div class="modal-content">
|
|
<!-- 玻璃样式的标题栏 -->
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">客户详情</h2>
|
|
<div>
|
|
<button class="action-btn" id="editCustomerBtn"><i class="fas fa-edit"></i> 编辑</button>
|
|
<button class="close-modal" id="closeModal">×</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 弹窗内容 -->
|
|
<div class="modal-body">
|
|
<div class="customer-details">
|
|
<!-- 公海需求部分,只对公海池客户显示 -->
|
|
<div class="detail-section public-sea-demand-section" id="publicSeaDemandSection" style="display: none;">
|
|
<h3 class="detail-section-title">公海需求</h3>
|
|
<div class="detail-item">
|
|
<span class="detail-label">产品名称:</span>
|
|
<span class="detail-value" id="detail-product-name">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">品种:</span>
|
|
<span class="detail-value" id="detail-variety">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">规格:</span>
|
|
<span class="detail-value" id="detail-specification">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">件数:</span>
|
|
<span class="detail-value" id="detail-quantity">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">毛重:</span>
|
|
<span class="detail-value" id="detail-weight">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">蛋黄:</span>
|
|
<span class="detail-value" id="detail-yolk">-</span>
|
|
</div>
|
|
<div class="detail-item" id="multipleDemandsBtn" style="display: none;">
|
|
<button class="action-btn" id="viewFullDemandsBtn">
|
|
<i class="fas fa-list"></i> 查看完整需求
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 基本联系人信息单独一行 -->
|
|
<div>
|
|
<div class="detail-section">
|
|
<h3 class="detail-section-title">
|
|
基本联系人
|
|
<span class="add-detail-container">
|
|
<button class="add-detail-btn" id="addDetailBtn">
|
|
<span class="plus-sign">+</span>
|
|
</button>
|
|
</span>
|
|
</h3>
|
|
<!-- 联系人信息 -->
|
|
<div class="detail-item">
|
|
<span class="detail-label">联系人:</span>
|
|
<span class="detail-value" id="detail-contact">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">电话:</span>
|
|
<span class="detail-value" id="detail-phone">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">微信号:</span>
|
|
<span class="detail-value" id="detail-wechat">-</span>
|
|
</div>
|
|
|
|
<!-- 新增的账户信息 -->
|
|
<div class="detail-item">
|
|
<span class="detail-label">账户:</span>
|
|
<span class="detail-value" id="detail-account">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">账号:</span>
|
|
<span class="detail-value" id="detail-account-number">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">开户行:</span>
|
|
<span class="detail-value" id="detail-bank">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">地址:</span>
|
|
<div class="address-container">
|
|
<span class="address-content" id="detail-address">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 公司信息单独一行 -->
|
|
<div>
|
|
<div class="detail-section">
|
|
<h3 class="detail-section-title">公司信息</h3>
|
|
<div class="detail-item">
|
|
<span class="detail-label">ID:</span>
|
|
<span class="detail-value" id="detail-id">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">客户公司:</span>
|
|
<span class="detail-value" id="detail-company">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">客户地区:</span>
|
|
<span class="detail-value" id="detail-region">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">客户等级:</span>
|
|
<span class="detail-value" id="detail-level">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">客户类型:</span>
|
|
<span class="detail-value" id="detail-type">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">基本需求:</span>
|
|
<span class="detail-value" id="detail-demand">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">规格:</span>
|
|
<span class="detail-value" id="detail-spec">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">创建时间:</span>
|
|
<span class="detail-value" id="detail-created-at">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">修改时间:</span>
|
|
<span class="detail-value" id="detail-updated-at">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 使用新的容器将负责人信息和附加联系人信息放在同一行,实现视觉平衡 -->
|
|
<div class="info-section-container">
|
|
<div>
|
|
<div class="detail-section">
|
|
<h3 class="detail-section-title">负责人信息</h3>
|
|
<div class="detail-item">
|
|
<span class="detail-label">公司:</span>
|
|
<span class="detail-value" id="detail-managercompany">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">部门:</span>
|
|
<span class="detail-value" id="detail-managerdepartment">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">组织:</span>
|
|
<span class="detail-value" id="detail-organization">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">职位:</span>
|
|
<span class="detail-value" id="detail-role">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">负责人:</span>
|
|
<span class="detail-value" id="detail-manager">-</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">协助人:</span>
|
|
<span class="detail-value" id="detail-assistant">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<!-- 自定义详情区域 - 调整为与基本信息一致的排版 -->
|
|
<div class="detail-section custom-details-section" id="customDetailsSection">
|
|
<h3 class="detail-section-title">附加联系人信息</h3>
|
|
<div id="customDetailsContainer">
|
|
<!-- 动态添加的详情将显示在这里 -->
|
|
<div class="detail-item"><span class="detail-value">暂无附加联系人信息</span></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 新增详情弹窗 -->
|
|
<div class="add-detail-modal" id="addDetailModal">
|
|
<div class="add-detail-modal-content">
|
|
<h3 class="add-detail-title">添加附加联系人信息</h3>
|
|
<div class="modal-input-group">
|
|
<label class="modal-label" for="newContactName">姓名</label>
|
|
<input type="text" id="newContactName" class="modal-input" placeholder="请输入联系人姓名">
|
|
</div>
|
|
<div class="modal-input-group">
|
|
<label class="modal-label" for="newContactPhone">电话</label>
|
|
<input type="text" id="newContactPhone" class="modal-input" placeholder="请输入联系电话">
|
|
</div>
|
|
<div class="modal-input-group">
|
|
<label class="modal-label" for="newContactAccount">账户</label>
|
|
<input type="text" id="newContactAccount" class="modal-input" placeholder="请输入账户名">
|
|
</div>
|
|
<div class="modal-input-group">
|
|
<label class="modal-label" for="newContactAccountNumber">账号</label>
|
|
<input type="text" id="newContactAccountNumber" class="modal-input" placeholder="请输入账号">
|
|
</div>
|
|
<div class="modal-input-group">
|
|
<label class="modal-label" for="newContactBank">开户行</label>
|
|
<input type="text" id="newContactBank" class="modal-input" placeholder="请输入开户行">
|
|
</div>
|
|
<div class="modal-input-group">
|
|
<label class="modal-label" for="newContactAddress">地址</label>
|
|
<input type="text" id="newContactAddress" class="modal-input" placeholder="请输入地址">
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button class="modal-btn cancel" id="cancelAddDetail">取消</button>
|
|
<button class="modal-btn confirm" id="confirmAddDetail">确定</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 跟进详情弹窗 -->
|
|
<div class="follow-up-detail-modal" id="followUpDetailModal">
|
|
<div class="follow-up-detail-content">
|
|
<div class="follow-up-detail-header">
|
|
<h2 class="follow-up-detail-title">跟进详情</h2>
|
|
<button class="close-follow-up-detail" id="closeFollowUpDetail">×</button>
|
|
</div>
|
|
<div class="follow-up-detail-body">
|
|
<!-- 简化后的跟进详情内容 -->
|
|
<div class="detail-section">
|
|
<h3 class="detail-section-title">跟进信息</h3>
|
|
<table class="follow-up-details-table">
|
|
<tr>
|
|
<th>跟进信息</th>
|
|
<td>
|
|
<textarea id="follow-up-content" rows="8" cols="50" placeholder="请输入跟进信息"></textarea>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="follow-up-actions">
|
|
<button id="saveFollowUpBtn" class="save-follow-up-btn">保存跟进信息</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 👇 在此处插入「新增客户弹窗」👇 -->
|
|
<div class="modal" id="addCustomerModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">新增客户</h2>
|
|
<button class="close-modal" id="closeAddCustomerModal">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="addCustomerForm">
|
|
<div class="customer-details">
|
|
<!-- 公司信息 -->
|
|
<div>
|
|
<div class="detail-section">
|
|
<h3 class="detail-section-title">公司信息</h3>
|
|
<div class="detail-item">
|
|
<span class="detail-label">ID:</span>
|
|
<input type="text" class="detail-input" id="add-id" readonly>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">客户公司<span class="required">*</span>:</span>
|
|
<input type="text" class="detail-input" id="add-company" placeholder="请输入客户公司" required>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">客户地区:</span>
|
|
<input type="text" class="detail-input" id="add-region" placeholder="请输入客户地区">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">客户等级<span class="required">*</span>:</span>
|
|
<div class="level-select-container customer-level-container">
|
|
<div class="level-select-trigger detail-input">
|
|
<div class="placeholder">请选择客户等级</div>
|
|
<input type="hidden" id="customer-levels" value="" required>
|
|
</div>
|
|
<div class="level-select-dropdown customer-level-dropdown">
|
|
<div class="level-select-option" data-value="important">重要客户</div>
|
|
<div class="level-select-option" data-value="normal">普通客户</div>
|
|
<div class="level-select-option" data-value="low-value">低价值客户</div>
|
|
<div class="level-select-option" data-value="logistics">物流客户</div>
|
|
<div class="level-select-option" data-value="unclassified">未分级客户</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">客户类型<span class="required">*</span>:</span>
|
|
<div class="level-select-container">
|
|
<div class="level-select-trigger detail-input">
|
|
<div class="placeholder">请选择客户类型</div>
|
|
<input type="hidden" id="add-type" value="" required>
|
|
</div>
|
|
<div class="level-select-dropdown">
|
|
<div class="level-select-option" data-value="客户端">客户端</div>
|
|
<div class="level-select-option" data-value="供应端">供应端</div>
|
|
<div class="level-select-option" data-value="BOTH">BOTH</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 基本需求多选选下拉框HTML -->
|
|
<div class="detail-item">
|
|
<span class="detail-label">基本需求<span class="required">*</span>:</span>
|
|
<div class="needs-select-container" id="needs-select-container">
|
|
<div class="needs-select-trigger detail-input" id="needs-select-trigger">
|
|
<div class="placeholder">请选择需求</div>
|
|
<input type="hidden" id="add-needs" value="" required>
|
|
</div>
|
|
<div class="needs-select-dropdown" id="needs-select-dropdown">
|
|
<!-- 添加全选/取消全选选项 -->
|
|
<div class="needs-select-option select-all" data-value="select-all">
|
|
<i class="check-icon"></i> 全选
|
|
</div>
|
|
<div class="needs-select-divider"></div>
|
|
<div class="needs-select-option" data-value="pink-egg">粉蛋</div>
|
|
<div class="needs-select-option" data-value="pink-three">粉三</div>
|
|
<div class="needs-select-option" data-value="red-egg">红蛋</div>
|
|
<div class="needs-select-option" data-value="green-shell">绿壳</div>
|
|
<div class="needs-select-option" data-value="native-egg">土鸡蛋</div>
|
|
<div class="needs-select-option" data-value="defective">次品</div>
|
|
<div class="needs-select-option" data-value="white-shell">白壳</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">规格:</span>
|
|
<input type="text" class="detail-input" id="add-spec" placeholder="请输入规格">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- 联系人信息 -->
|
|
<div>
|
|
<div class="detail-section">
|
|
<h3 class="detail-section-title">基本联系人</h3>
|
|
<div class="detail-item">
|
|
<span class="detail-label">联系人<span class="required">*</span>:</span>
|
|
<input type="text" class="detail-input" id="add-contact" placeholder="请输入联系人" required>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">电话<span class="required">*</span>:</span>
|
|
<input type="text" class="detail-input" id="add-phone" placeholder="请输入电话" required>
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">微信号:</span>
|
|
<input type="text" class="detail-input" id="add-wechat" placeholder="请输入微信号">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">账户:</span>
|
|
<input type="text" class="detail-input" id="add-account" placeholder="请输入账户">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">账号:</span>
|
|
<input type="text" class="detail-input" id="add-account-number" placeholder="请输入账号">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">开户行:</span>
|
|
<input type="text" class="detail-input" id="add-bank" placeholder="请输入开户行">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">地址:</span>
|
|
<input type="text" class="detail-input" id="add-address" placeholder="请输入地址">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="detail-section">
|
|
<h3 class="detail-section-title">负责人信息</h3>
|
|
<div class="detail-item">
|
|
<span class="detail-label">公司:</span>
|
|
<input type="text" class="detail-input" id="add-managercompany" placeholder="自动填充登录信息">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">部门:</span>
|
|
<input type="text" class="detail-input" id="add-managerdepartment" placeholder="自动填充登录信息">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">组织:</span>
|
|
<input type="text" class="detail-input" id="add-organization" placeholder="自动填充登录信息">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">职位:</span>
|
|
<input type="text" class="detail-input" id="add-role" placeholder="自动填充登录信息">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">负责人:</span>
|
|
<input type="text" class="detail-input" id="add-userName" placeholder="自动填充登录信息">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">协助人:</span>
|
|
<input type="text" class="detail-input" id="add-assistant" placeholder="自动填充登录信息">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-actions">
|
|
<button type="button" class="modal-btn cancel" id="cancelAddCustomer">取消</button>
|
|
<button type="submit" class="modal-btn confirm" id="confirmAddCustomer">确定</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="refresh-status" id="refreshStatus">
|
|
<i class="fas fa-check-circle"></i> 数据已刷新
|
|
</div>
|
|
<!-- 公海需求选择弹窗 -->
|
|
<div class="modal" id="publicSeaDemandsModal">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h2 class="modal-title">选择公海需求</h2>
|
|
<button class="close-modal" id="closePublicSeaDemandsModal">×</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="publicSeaDemandsContainer" style="display: flex; flex-wrap: wrap; gap: 15px; justify-content: center;">
|
|
<!-- 需求卡片将动态生成在这里 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
|
|
|
|
<script>
|
|
|
|
if (typeof debugParticles === 'undefined') {
|
|
console.log('检测到粒子系统未加载,正在启动...');
|
|
|
|
// ============ 在这里添加状态锁变量 ============
|
|
let isUpdatingPagination = false;
|
|
let pendingPaginationUpdate = false;
|
|
// ===========================================
|
|
|
|
// ==================== 3D粒子特效 - 复杂版 ====================
|
|
console.log('🔮 加载复杂粒子特效模块...');
|
|
|
|
// 全局控制函数
|
|
window.toggleParticles = function () {
|
|
console.log('toggleParticles被调用');
|
|
const canvas = document.getElementById('particleCanvas');
|
|
if (!canvas) {
|
|
console.error('找不到粒子画布');
|
|
return;
|
|
}
|
|
|
|
if (canvas.style.display === 'none' || !window.particleAnimationId) {
|
|
console.log('开启粒子特效');
|
|
canvas.style.display = 'block';
|
|
if (!window.particleAnimationId) {
|
|
initComplexParticleSystem();
|
|
}
|
|
} else {
|
|
console.log('关闭粒子特效');
|
|
canvas.style.display = 'none';
|
|
if (window.particleAnimationId) {
|
|
cancelAnimationFrame(window.particleAnimationId);
|
|
window.particleAnimationId = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
window.debugParticles = function () {
|
|
console.log('=== 粒子系统调试 ===');
|
|
console.log('Three.js:', typeof THREE);
|
|
console.log('Canvas:', document.getElementById('particleCanvas'));
|
|
console.log('动画状态:', window.particleAnimationId ? '运行中' : '未运行');
|
|
console.log('==================');
|
|
};
|
|
|
|
// 复杂粒子系统初始化
|
|
function initComplexParticleSystem() {
|
|
console.log('初始化复杂粒子系统...');
|
|
|
|
const canvas = document.getElementById('particleCanvas');
|
|
if (!canvas) {
|
|
console.error('找不到canvas元素');
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// 设置canvas样式
|
|
canvas.style.cssText = `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
z-index: -1;
|
|
display: block;
|
|
pointer-events: none;
|
|
`;
|
|
|
|
// 创建Three.js核心对象
|
|
const scene = new THREE.Scene();
|
|
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
|
|
const renderer = new THREE.WebGLRenderer({
|
|
canvas: canvas,
|
|
antialias: true,
|
|
alpha: true
|
|
});
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
renderer.setClearColor(0x000000, 0);
|
|
camera.position.z = 5;
|
|
|
|
// 粒子系统参数
|
|
const particleCount = 120;
|
|
const minDistance = 2.5;
|
|
const particleSize = 0.2;
|
|
const particles = new THREE.BufferGeometry();
|
|
const positions = new Float32Array(particleCount * 3);
|
|
const colors = new Float32Array(particleCount * 3);
|
|
const sizes = new Float32Array(particleCount);
|
|
const velocities = new Float32Array(particleCount * 3);
|
|
const initialPositions = new Float32Array(particleCount * 3);
|
|
const opacities = new Float32Array(particleCount);
|
|
const lifeCycles = new Float32Array(particleCount);
|
|
const resetOffsets = new Float32Array(particleCount);
|
|
const acceleration = new Float32Array(particleCount);
|
|
const maxSpeed = new Float32Array(particleCount);
|
|
|
|
const maxLife = 5000;
|
|
const fadeRange = 600;
|
|
const baseSpeed = 0.008;
|
|
const accelerationFactor = 0.000050;
|
|
const maxBaseSpeed = 0.008;
|
|
const frustum = new THREE.Frustum();
|
|
const distanceThreshold = 200;
|
|
const distributionRadius = 14;
|
|
|
|
const colorPalette = [
|
|
new THREE.Color(0xe86a92),
|
|
new THREE.Color(0xd68fb7),
|
|
new THREE.Color(0x8e44ad),
|
|
new THREE.Color(0xe74c3c)
|
|
];
|
|
|
|
// 位置验证函数
|
|
function isPositionValid(x, y, z, existingParticles, index) {
|
|
for (let i = 0; i < index; i++) {
|
|
const i3 = i * 3;
|
|
const dx = x - existingParticles[i3];
|
|
const dy = y - existingParticles[i3 + 1];
|
|
const dz = z - existingParticles[i3 + 2];
|
|
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
|
|
if (distance < minDistance) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// 初始化粒子位置
|
|
let particleIndex = 0;
|
|
const maxAttempts = 100;
|
|
const rings = 6;
|
|
const particlesPerRing = Math.ceil(particleCount / rings);
|
|
|
|
for (let ring = 1; ring <= rings; ring++) {
|
|
const radius = (distributionRadius / (rings + 1)) * ring;
|
|
|
|
for (let p = 0; p < particlesPerRing && particleIndex < particleCount; p++) {
|
|
let validPosition = false;
|
|
let x, y, z;
|
|
let attempts = 0;
|
|
|
|
while (!validPosition && attempts < maxAttempts) {
|
|
const angle = (p / particlesPerRing) * Math.PI * 2 + (Math.random() * 0.1);
|
|
x = Math.cos(angle) * radius;
|
|
y = Math.sin(angle) * radius;
|
|
z = -Math.random() * 4;
|
|
|
|
validPosition = isPositionValid(x, y, z, positions, particleIndex);
|
|
attempts++;
|
|
}
|
|
|
|
addParticle(x, y, z);
|
|
}
|
|
}
|
|
|
|
// 添加单个粒子
|
|
function addParticle(x, y, z) {
|
|
const i3 = particleIndex * 3;
|
|
|
|
positions[i3] = x;
|
|
positions[i3 + 1] = y;
|
|
positions[i3 + 2] = z;
|
|
|
|
initialPositions[i3] = x;
|
|
initialPositions[i3 + 1] = y;
|
|
initialPositions[i3 + 2] = z;
|
|
|
|
const distanceFromCenter = Math.sqrt(x * x + y * y) || 1;
|
|
const speedFactor = 0.025 / distanceFromCenter;
|
|
|
|
velocities[i3] = x * speedFactor * baseSpeed;
|
|
velocities[i3 + 1] = y * speedFactor * baseSpeed;
|
|
velocities[i3 + 2] = baseSpeed * (1 + Math.random() * 0.2);
|
|
|
|
acceleration[particleIndex] = 1.0 + Math.random() * 0.5;
|
|
maxSpeed[particleIndex] = maxBaseSpeed * (0.8 + Math.random() * 0.4);
|
|
sizes[particleIndex] = particleSize * (0.9 + Math.random() * 0.2);
|
|
|
|
const color = colorPalette[Math.floor(Math.random() * colorPalette.length)];
|
|
colors[i3] = color.r;
|
|
colors[i3 + 1] = color.g;
|
|
colors[i3 + 2] = color.b;
|
|
|
|
lifeCycles[particleIndex] = Math.random() * maxLife;
|
|
resetOffsets[particleIndex] = Math.random() * 1000;
|
|
opacities[particleIndex] = 1;
|
|
|
|
particleIndex++;
|
|
}
|
|
|
|
// 设置粒子几何体属性
|
|
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
particles.setAttribute('color', new THREE.BufferAttribute(colors, 3));
|
|
particles.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
|
|
particles.setAttribute('opacity', new THREE.BufferAttribute(opacities, 1));
|
|
|
|
// 创建自定义着色器材质
|
|
const particleMaterial = new THREE.ShaderMaterial({
|
|
uniforms: {
|
|
color: { value: new THREE.Color(0xffffff) }
|
|
},
|
|
vertexShader: `
|
|
attribute float size;
|
|
attribute float opacity;
|
|
varying float vOpacity;
|
|
void main() {
|
|
vOpacity = opacity;
|
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
gl_PointSize = size * (150.0 / -mvPosition.z);
|
|
gl_Position = projectionMatrix * mvPosition;
|
|
}
|
|
`,
|
|
fragmentShader: `
|
|
uniform vec3 color;
|
|
varying float vOpacity;
|
|
void main() {
|
|
vec2 coord = gl_PointCoord - vec2(0.5);
|
|
float distance = length(coord);
|
|
|
|
float smoothness = 0.2;
|
|
float alpha = vOpacity * (1.0 - smoothstep(0.5 - smoothness, 0.5, distance));
|
|
|
|
alpha *= 1.0 - (distance * 1.5);
|
|
|
|
if (alpha < 0.05) discard;
|
|
|
|
gl_FragColor = vec4(color, alpha);
|
|
}
|
|
`,
|
|
transparent: true,
|
|
blending: THREE.AdditiveBlending,
|
|
alphaTest: 0.05
|
|
});
|
|
|
|
const particleSystem = new THREE.Points(particles, particleMaterial);
|
|
scene.add(particleSystem);
|
|
|
|
const vector = new THREE.Vector3();
|
|
|
|
// 动画循环函数
|
|
function animate() {
|
|
window.particleAnimationId = requestAnimationFrame(animate);
|
|
|
|
camera.updateMatrixWorld();
|
|
frustum.setFromProjectionMatrix(
|
|
new THREE.Matrix4().multiplyMatrices(
|
|
camera.projectionMatrix,
|
|
camera.matrixWorldInverse
|
|
)
|
|
);
|
|
|
|
const positions = particles.attributes.position.array;
|
|
const opacities = particles.attributes.opacity.array;
|
|
|
|
for (let i = 0; i < particleCount; i++) {
|
|
const i3 = i * 3;
|
|
|
|
lifeCycles[i]++;
|
|
|
|
const effectiveMaxLife = maxLife + resetOffsets[i];
|
|
if (lifeCycles[i] < fadeRange) {
|
|
opacities[i] = lifeCycles[i] / fadeRange;
|
|
} else if (lifeCycles[i] > effectiveMaxLife - fadeRange) {
|
|
opacities[i] = (effectiveMaxLife - lifeCycles[i]) / fadeRange;
|
|
} else {
|
|
opacities[i] = 1;
|
|
}
|
|
|
|
const lifeProgress = lifeCycles[i] / effectiveMaxLife;
|
|
|
|
let speedMultiplier;
|
|
if (lifeProgress < 0.3) {
|
|
speedMultiplier = 1.0 + (lifeProgress / 0.3) * 0.5;
|
|
} else {
|
|
speedMultiplier = 1.5 - ((lifeProgress - 0.3) / 0.7) * 0.7;
|
|
}
|
|
|
|
const currentSpeedX = velocities[i3] * speedMultiplier;
|
|
const currentSpeedY = velocities[i3 + 1] * speedMultiplier;
|
|
const currentSpeedZ = velocities[i3 + 2] * speedMultiplier;
|
|
|
|
const speedMagnitude = Math.sqrt(currentSpeedX * currentSpeedX +
|
|
currentSpeedY * currentSpeedY +
|
|
currentSpeedZ * currentSpeedZ);
|
|
|
|
if (speedMagnitude > maxSpeed[i]) {
|
|
const scaleFactor = maxSpeed[i] / speedMagnitude;
|
|
positions[i3] += currentSpeedX * scaleFactor;
|
|
positions[i3 + 1] += currentSpeedY * scaleFactor;
|
|
positions[i3 + 2] += currentSpeedZ * scaleFactor;
|
|
} else {
|
|
positions[i3] += currentSpeedX;
|
|
positions[i3 + 1] += currentSpeedY;
|
|
positions[i3 + 2] += currentSpeedZ;
|
|
}
|
|
|
|
vector.set(positions[i3], positions[i3 + 1], positions[i3 + 2]);
|
|
const isOutsideFrustum = !frustum.containsPoint(vector);
|
|
const distanceFromCamera = vector.distanceTo(camera.position);
|
|
if (lifeCycles[i] > effectiveMaxLife ||
|
|
distanceFromCamera > distanceThreshold ||
|
|
isOutsideFrustum) {
|
|
|
|
let x, y, z;
|
|
let validPosition = false;
|
|
let attempts = 0;
|
|
|
|
while (!validPosition && attempts < maxAttempts) {
|
|
const ring = Math.floor(Math.random() * rings) + 1;
|
|
const radius = (distributionRadius / (rings + 1)) * ring;
|
|
const angle = Math.random() * Math.PI * 2;
|
|
|
|
x = Math.cos(angle) * radius;
|
|
y = Math.sin(angle) * radius;
|
|
z = -Math.random() * 4;
|
|
|
|
validPosition = true;
|
|
for (let j = 0; j < particleCount; j++) {
|
|
if (j === i) continue;
|
|
const j3 = j * 3;
|
|
const dx = x - positions[j3];
|
|
const dy = y - positions[j3 + 1];
|
|
const dz = z - positions[j3 + 2];
|
|
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
|
|
if (distance < minDistance && lifeCycles[j] > fadeRange && lifeCycles[j] < effectiveMaxLife - fadeRange) {
|
|
validPosition = false;
|
|
break;
|
|
}
|
|
}
|
|
attempts++;
|
|
}
|
|
|
|
positions[i3] = x;
|
|
positions[i3 + 1] = y;
|
|
positions[i3 + 2] = z;
|
|
lifeCycles[i] = 0;
|
|
}
|
|
}
|
|
|
|
particles.attributes.position.needsUpdate = true;
|
|
particles.attributes.opacity.needsUpdate = true;
|
|
|
|
particleSystem.rotation.x += 0.0000005;
|
|
particleSystem.rotation.y += 0.000001;
|
|
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
// 启动动画
|
|
animate();
|
|
|
|
console.log('✅ 复杂粒子系统初始化成功');
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('❌ 复杂粒子系统初始化失败:', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 页面加载后自动启动
|
|
function startComplexParticlesOnLoad() {
|
|
console.log('🕒 准备启动复杂粒子系统...');
|
|
|
|
// 确保Three.js已加载
|
|
if (typeof THREE === 'undefined') {
|
|
console.error('Three.js未加载,无法启动粒子系统');
|
|
return;
|
|
}
|
|
|
|
// 确保DOM已加载
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
console.log('📄 DOM加载完成,启动复杂粒子系统');
|
|
setTimeout(initComplexParticleSystem, 100);
|
|
});
|
|
} else {
|
|
console.log('📄 DOM已就绪,启动复杂粒子系统');
|
|
setTimeout(initComplexParticleSystem, 100);
|
|
}
|
|
}
|
|
|
|
// 窗口大小调整处理
|
|
window.addEventListener('resize', function () {
|
|
// 可以在这里添加窗口大小调整的处理
|
|
});
|
|
|
|
// 立即开始初始化流程
|
|
startComplexParticlesOnLoad();
|
|
|
|
console.log('🎯 复杂粒子特效模块加载完成');
|
|
// ==================== 复杂粒子特效代码结束 ====================
|
|
}
|
|
|
|
|
|
|
|
// ==================== 客户数据缓存系统 ====================
|
|
class CustomerDataCache {
|
|
constructor() {
|
|
this.cache = new Map();
|
|
this.timestamps = new Map();
|
|
this.cacheTTL = 1 * 60 * 1000; // 2分钟缓存时间
|
|
this.isUpdating = false;
|
|
this.currentUpdateLevel = null; // 添加当前更新等级锁定
|
|
this.updateInterval = null;
|
|
this.lastUpdateTime = null; // 添加最后更新时间跟踪
|
|
}
|
|
|
|
// 初始化缓存系统
|
|
init() {
|
|
console.log('🔄 初始化客户数据缓存系统');
|
|
this.startAutoRefresh();
|
|
|
|
// 移除预加载所有等级数据,改为按需加载
|
|
}
|
|
|
|
// 预加载所有客户等级数据 - 优化为并行加载
|
|
async preloadAllLevels() {
|
|
// 分离为不同的数据来源类型,减少重复API调用
|
|
const publicSeaLevels = ['company-sea-pools', 'organization-sea-pools', 'department-sea-pools'];
|
|
const normalLevels = ['important', 'normal', 'low-value', 'logistics', 'unclassified'];
|
|
|
|
console.log('📥 开始预加载客户数据');
|
|
|
|
try {
|
|
// 并行加载公海池数据
|
|
const publicSeaPromise = this.refreshPublicSeaLevels(publicSeaLevels);
|
|
|
|
// 并行加载普通等级数据(只调用一次API)
|
|
const normalLevelsPromise = this.refreshNormalLevels(normalLevels);
|
|
|
|
// 加载全部客户数据
|
|
const allCustomersPromise = this.refreshLevelData('all');
|
|
|
|
// 等待所有并行任务完成
|
|
await Promise.all([publicSeaPromise, normalLevelsPromise, allCustomersPromise]);
|
|
|
|
console.log('✅ 所有等级数据预加载完成');
|
|
} catch (error) {
|
|
console.warn('预加载部分数据失败:', error);
|
|
}
|
|
}
|
|
|
|
// 批量刷新公海池等级数据
|
|
async refreshPublicSeaLevels(levels) {
|
|
try {
|
|
// 只调用一次API获取所有公海池数据
|
|
const loginInfo = getLoginInfo();
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/all-customers`);
|
|
const response = await fetch(url);
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
|
|
|
|
const result = await response.json();
|
|
if (!result.success) throw new Error(result.message);
|
|
|
|
const data = result.data || {};
|
|
const allPublicSeaCustomers = Array.isArray(data) ? data : Object.values(data);
|
|
|
|
// 统计notice为old的客户数量并更新通知铃铛
|
|
this.updateNotificationStatus(allPublicSeaCustomers);
|
|
|
|
// 保存所有公海客户数据到缓存中,用于通知弹窗
|
|
this.allPublicSeaCustomers = allPublicSeaCustomers;
|
|
|
|
// 对每个等级分别过滤和缓存
|
|
for (const level of levels) {
|
|
const filteredCustomers = this.filterPublicSeaCustomersByLoginInfo(
|
|
allPublicSeaCustomers, loginInfo, level
|
|
).sort((a, b) => {
|
|
const aTime = new Date(a.updated_at || a.created_at);
|
|
const bTime = new Date(b.updated_at || b.created_at);
|
|
return bTime - aTime;
|
|
});
|
|
|
|
this.set(level, filteredCustomers);
|
|
this.updateUIIfActive(level, filteredCustomers);
|
|
}
|
|
} catch (error) {
|
|
console.error(`❌ 刷新公海池数据失败:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 批量刷新普通等级数据
|
|
async refreshNormalLevels(levels) {
|
|
try {
|
|
// 只调用一次API获取所有数据
|
|
const loginInfo = getLoginInfo();
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/customers`);
|
|
const response = await fetch(url);
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
|
|
|
|
const result = await response.json();
|
|
if (!result.success) throw new Error(result.message);
|
|
|
|
const dataMap = result.data || {};
|
|
const allCustomers = Object.values(dataMap);
|
|
|
|
// 根据登录信息过滤一次
|
|
const filteredByLogin = this.filterCustomersByLoginInfo(
|
|
allCustomers, loginInfo, null
|
|
);
|
|
|
|
// 对每个等级分别过滤和缓存
|
|
for (const level of levels) {
|
|
const levelMap = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'low-value': 'low-value',
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified'
|
|
};
|
|
|
|
const backendLevel = levelMap[level] || level;
|
|
|
|
const customersForLevel = filteredByLogin.filter(customer => {
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
return customerLevel === backendLevel;
|
|
}).sort((a, b) => {
|
|
const aTime = new Date(a.updated_at || a.created_at);
|
|
const bTime = new Date(b.updated_at || b.created_at);
|
|
return bTime - aTime;
|
|
});
|
|
|
|
this.set(level, customersForLevel);
|
|
this.updateUIIfActive(level, customersForLevel);
|
|
}
|
|
} catch (error) {
|
|
console.error(`❌ 刷新普通等级数据失败:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 获取缓存数据 - 优化版
|
|
get(level) {
|
|
const cachedData = this.cache.get(level);
|
|
const timestamp = this.timestamps.get(level);
|
|
|
|
if (!cachedData || !timestamp) {
|
|
console.log(`❌ 缓存未命中: ${level}`);
|
|
// 缓存未命中时,尝试从'all'数据中过滤,提升切换等级的响应速度
|
|
const allData = this.cache.get('all');
|
|
if (allData && level !== 'all') {
|
|
console.log(`🔄 从'all'缓存中过滤: ${level}数据`);
|
|
const filteredData = this.filterDataByLevel(allData, level);
|
|
// 异步刷新真实数据,但立即使用过滤数据显示
|
|
this.refreshLevelData(level).catch(err => console.warn(`刷新${level}数据失败:`, err));
|
|
return filteredData;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// 检查缓存是否过期
|
|
const isExpired = Date.now() - timestamp > this.cacheTTL;
|
|
if (isExpired) {
|
|
console.log(`⏰ 缓存已过期: ${level}`);
|
|
this.refreshLevelData(level); // 异步刷新,但先返回过期数据
|
|
return cachedData;
|
|
}
|
|
|
|
console.log(`✅ 缓存命中: ${level}, 数据量: ${cachedData.length}`);
|
|
return cachedData;
|
|
}
|
|
|
|
// 辅助方法:根据等级从全部数据中过滤
|
|
filterDataByLevel(allData, level) {
|
|
const levelMap = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'low-value': 'low-value',
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified'
|
|
};
|
|
|
|
const backendLevel = levelMap[level] || level;
|
|
|
|
return allData.filter(customer => {
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
return customerLevel === backendLevel;
|
|
}).sort((a, b) => {
|
|
const aTime = new Date(a.updated_at || a.created_at).getTime();
|
|
const bTime = new Date(b.updated_at || b.created_at).getTime();
|
|
return bTime - aTime;
|
|
});
|
|
}
|
|
|
|
// 设置缓存数据
|
|
set(level, data) {
|
|
// 检查数据是否与现有缓存相同
|
|
const existingData = this.cache.get(level);
|
|
if (existingData && this.isDataEqual(existingData, data)) {
|
|
console.log(`🔄 数据未变化,跳过缓存更新: ${level}`);
|
|
this.timestamps.set(level, Date.now()); // 更新时间戳但保持数据
|
|
return;
|
|
}
|
|
|
|
this.cache.set(level, data);
|
|
this.timestamps.set(level, Date.now());
|
|
this.lastUpdateTime = Date.now();
|
|
console.log(`💾 缓存已更新: ${level}, 数据量: ${data.length}`);
|
|
}
|
|
|
|
// 检查数据是否相等
|
|
isDataEqual(data1, data2) {
|
|
if (!data1 || !data2) return false;
|
|
if (data1.length !== data2.length) return false;
|
|
|
|
// 简单比较:检查第一个和最后一个元素的ID
|
|
if (data1.length > 0 && data2.length > 0) {
|
|
const firstEqual = data1[0]?.id === data2[0]?.id;
|
|
const lastEqual = data1[data1.length-1]?.id === data2[data2.length-1]?.id;
|
|
return firstEqual && lastEqual;
|
|
}
|
|
|
|
return JSON.stringify(data1) === JSON.stringify(data2);
|
|
}
|
|
// 刷新特定等级数据 - 优化版
|
|
async refreshLevelData(level) {
|
|
// 使用锁避免重复更新
|
|
if (this.isUpdating && this.currentUpdateLevel === level) {
|
|
console.log('⏳ 同一等级更新已在进行中,跳过');
|
|
return;
|
|
}
|
|
|
|
// 检查是否需要刷新(避免频繁刷新)
|
|
const lastTimestamp = this.timestamps.get(level);
|
|
if (lastTimestamp && Date.now() - lastTimestamp < 30000) { // 30秒内不重复刷新
|
|
// 如果数据已缓存但用户正在查看该等级,至少返回缓存数据
|
|
const cachedData = this.cache.get(level);
|
|
if (cachedData) {
|
|
this.updateUIIfActive(level, cachedData);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 设置更新状态
|
|
this.isUpdating = true;
|
|
this.currentUpdateLevel = level;
|
|
console.log(`🔄 刷新等级数据: ${level}`);
|
|
|
|
try {
|
|
const loginInfo = getLoginInfo();
|
|
let customersArray;
|
|
|
|
// 根据数据类型使用不同的处理逻辑
|
|
const publicSeaLevels = ['company-sea-pools', 'organization-sea-pools', 'department-sea-pools'];
|
|
const normalLevels = ['important', 'normal', 'low-value', 'logistics', 'unclassified'];
|
|
|
|
if (publicSeaLevels.includes(level)) {
|
|
// 公海池数据
|
|
customersArray = await this.fetchPublicSeaData(loginInfo, level);
|
|
} else if (normalLevels.includes(level) || level === 'all') {
|
|
// 普通等级或全部数据
|
|
customersArray = await this.fetchNormalLevelData(loginInfo, level);
|
|
} else {
|
|
console.error(`❌ 未知的等级类型: ${level}`);
|
|
return;
|
|
}
|
|
|
|
// 对数据进行排序
|
|
const sortedCustomers = customersArray.sort((a, b) => {
|
|
const aTime = new Date(a.updated_at || a.created_at).getTime();
|
|
const bTime = new Date(b.updated_at || b.created_at).getTime();
|
|
return bTime - aTime;
|
|
});
|
|
|
|
// 更新缓存(会检查是否重复)
|
|
this.set(level, sortedCustomers);
|
|
|
|
// 如果当前正在查看这个等级,立即更新UI
|
|
this.updateUIIfActive(level, sortedCustomers);
|
|
|
|
} catch (error) {
|
|
console.error(`❌ 刷新等级数据失败 (${level}):`, error);
|
|
} finally {
|
|
// 重置更新状态
|
|
this.isUpdating = false;
|
|
this.currentUpdateLevel = null;
|
|
}
|
|
}
|
|
|
|
// 获取公海池数据的辅助方法
|
|
// 更新通知铃铛状态
|
|
updateNotificationStatus(customers) {
|
|
// 统计notice为banold的客户数量
|
|
console.log('📊 更新通知状态 - 客户列表:', customers.length, '个客户');
|
|
const banoldCount = customers.filter(customer => customer.notice === 'banold').length;
|
|
console.log('🔔 待处理通知数量:', banoldCount);
|
|
|
|
// 更新通知铃铛样式
|
|
const notificationButton = document.getElementById('notificationButton');
|
|
console.log('🔘 通知按钮存在:', !!notificationButton);
|
|
if (notificationButton) {
|
|
const bellIcon = notificationButton.querySelector('i');
|
|
if (bellIcon) {
|
|
if (banoldCount > 0) {
|
|
notificationButton.classList.add('notification-active');
|
|
bellIcon.style.animation = 'ring 1s ease-in-out';
|
|
|
|
// 添加或更新通知数量显示
|
|
let countBadge = notificationButton.querySelector('.notification-count');
|
|
if (!countBadge) {
|
|
countBadge = document.createElement('span');
|
|
countBadge.className = 'notification-count';
|
|
notificationButton.appendChild(countBadge);
|
|
}
|
|
countBadge.textContent = banoldCount;
|
|
} else {
|
|
notificationButton.classList.remove('notification-active');
|
|
bellIcon.style.animation = 'none';
|
|
|
|
// 移除通知数量显示
|
|
const countBadge = notificationButton.querySelector('.notification-count');
|
|
if (countBadge) {
|
|
countBadge.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 绑定通知点击事件
|
|
this.bindNotificationEvents();
|
|
}
|
|
|
|
return banoldCount;
|
|
}
|
|
|
|
async fetchPublicSeaData(loginInfo, level) {
|
|
const url = appendAuthParams(`${API_BASE_URL}/supply/pool/all-customers`);
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
// 添加缓存控制
|
|
'Cache-Control': 'no-cache'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
|
|
const result = await response.json();
|
|
if (!result.success) throw new Error(result.message);
|
|
|
|
const data = result.data || {};
|
|
const customersArray = Array.isArray(data) ? data : Object.values(data);
|
|
|
|
// 统计notice为old的客户数量并更新通知铃铛
|
|
this.updateNotificationStatus(customersArray);
|
|
|
|
// 根据登录信息过滤公海池数据
|
|
return this.filterPublicSeaCustomersByLoginInfo(customersArray, loginInfo, level);
|
|
}
|
|
|
|
// 绑定通知事件
|
|
bindNotificationEvents() {
|
|
const notificationButton = document.getElementById('notificationButton');
|
|
const notificationModal = document.getElementById('notificationModal');
|
|
const closeNotificationModal = document.getElementById('closeNotificationModal');
|
|
|
|
if (notificationButton && notificationModal && closeNotificationModal) {
|
|
// 移除现有的点击事件监听器,避免重复绑定
|
|
notificationButton.onclick = null;
|
|
closeNotificationModal.onclick = null;
|
|
|
|
// 点击通知按钮显示弹窗
|
|
notificationButton.addEventListener('click', () => {
|
|
this.showNotificationModal();
|
|
});
|
|
|
|
// 点击关闭按钮关闭弹窗
|
|
closeNotificationModal.addEventListener('click', () => {
|
|
this.hideNotificationModal();
|
|
});
|
|
|
|
// 点击弹窗外部关闭弹窗
|
|
notificationModal.addEventListener('click', (e) => {
|
|
if (e.target === notificationModal) {
|
|
this.hideNotificationModal();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 显示通知弹窗
|
|
async showNotificationModal() {
|
|
console.log('🎯 显示通知弹窗');
|
|
const notificationModal = document.getElementById('notificationModal');
|
|
const notificationContent = document.getElementById('notificationContent');
|
|
|
|
// 显示加载状态
|
|
notificationContent.innerHTML = '<p style="text-align: center; color: #666;">加载中...</p>';
|
|
notificationModal.classList.add('active');
|
|
|
|
// 阻止背景滚动和操作
|
|
document.body.style.overflow = 'hidden';
|
|
notificationModal.style.zIndex = '1000'; // 确保弹窗在最上层
|
|
|
|
try {
|
|
// 先尝试从缓存获取数据
|
|
let allCustomers = [];
|
|
if (this.allPublicSeaCustomers) {
|
|
allCustomers = this.allPublicSeaCustomers;
|
|
console.log('📋 从缓存获取客户数据:', allCustomers.length, '条');
|
|
console.log('📋 缓存完整数据:', JSON.stringify(this.allPublicSeaCustomers));
|
|
} else {
|
|
// 如果缓存中没有数据,直接从API获取
|
|
const url = appendAuthParams(`${API_BASE_URL}/supply/pool/all-customers`);
|
|
console.log('🌐 请求API地址:', url);
|
|
const response = await fetch(url);
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
|
|
|
|
const result = await response.json();
|
|
console.log('📥 API完整响应:', JSON.stringify(result));
|
|
|
|
if (!result.success) throw new Error(result.message);
|
|
|
|
const data = result.data || {};
|
|
allCustomers = Array.isArray(data) ? data : Object.values(data);
|
|
console.log('🔄 转换后客户数组:', allCustomers.length, '条');
|
|
console.log('🔄 转换后完整数据:', JSON.stringify(allCustomers));
|
|
|
|
// 更新缓存
|
|
this.allPublicSeaCustomers = allCustomers;
|
|
}
|
|
|
|
// 获取notice为banold的客户
|
|
const banoldCustomers = allCustomers.filter(customer => customer.notice === 'banold');
|
|
console.log('🔔 符合条件的banold客户:', banoldCustomers.length, '条');
|
|
console.log('🔔 banold客户详情:', JSON.stringify(banoldCustomers));
|
|
|
|
if (banoldCustomers.length === 0) {
|
|
notificationContent.innerHTML = '<div class="notification-empty"><p>暂无通知</p></div>';
|
|
} else {
|
|
// 生成通知内容
|
|
let contentHTML = '<div class="notification-list">';
|
|
banoldCustomers.forEach(customer => {
|
|
const customerName = customer.company || customer.companyName || '未知';
|
|
// 为banold状态(未读)添加new类
|
|
const notificationClass = customer.notice === 'banold' ? 'notification-item new' : 'notification-item';
|
|
contentHTML += `
|
|
<div class="${notificationClass}" onclick="viewCustomerDetails('${customer.id}', '${customer.phoneNumber || ''}', null, true)">
|
|
<div class="notification-header">
|
|
<div class="notification-icon">
|
|
<i class="fas fa-user-clock" style="color: #ff9800;"></i>
|
|
</div>
|
|
<div class="notification-content">
|
|
<div class="notification-title">
|
|
${customerName}
|
|
</div>
|
|
<div class="notification-meta">
|
|
<span class="customer-id">客户ID: ${customer.id}</span>
|
|
<span class="customer-phone">${customer.phoneNumber || '无电话'}</span>
|
|
</div>
|
|
<div class="notification-footer">
|
|
<div class="notification-time">${new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })}</div>
|
|
<div class="notification-status">未读</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
contentHTML += '</div>';
|
|
notificationContent.innerHTML = contentHTML;
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ 获取通知数据失败:', error);
|
|
console.error('❌ 错误详情:', error.stack);
|
|
notificationContent.innerHTML = '<p style="text-align: center; color: #ff6b6b;">加载通知失败,请稍后重试</p>';
|
|
}
|
|
}
|
|
|
|
// 隐藏通知弹窗
|
|
hideNotificationModal() {
|
|
const notificationModal = document.getElementById('notificationModal');
|
|
notificationModal.classList.remove('active');
|
|
|
|
// 恢复背景滚动
|
|
document.body.style.overflow = 'auto';
|
|
}
|
|
|
|
|
|
|
|
// 获取普通等级数据的辅助方法
|
|
async fetchNormalLevelData(loginInfo, level) {
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/customers`);
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
// 添加缓存控制
|
|
'Cache-Control': 'no-cache'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
|
|
const result = await response.json();
|
|
if (!result.success) throw new Error(result.message);
|
|
|
|
const dataMap = result.data || {};
|
|
let allCustomers = Object.values(dataMap);
|
|
|
|
// 统计notice为old的客户数量并更新通知铃铛
|
|
this.updateNotificationStatus(allCustomers);
|
|
|
|
// 根据登录信息和等级过滤数据
|
|
allCustomers = this.filterCustomersByLoginInfo(allCustomers, loginInfo, level);
|
|
|
|
// 如果是特定等级,进一步过滤
|
|
if (level !== 'all') {
|
|
const backendLevel = level;
|
|
allCustomers = allCustomers.filter(customer => {
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
return customerLevel === backendLevel;
|
|
});
|
|
}
|
|
|
|
return allCustomers;
|
|
}
|
|
|
|
// 过滤公海池客户数据
|
|
filterPublicSeaCustomersByLoginInfo(customers, loginInfo, level) {
|
|
return customers.filter(customer => {
|
|
switch (level) {
|
|
case 'company-sea-pools':
|
|
return true; // 公司公海池:所有人都能看到
|
|
case 'organization-sea-pools':
|
|
return customer.organization === loginInfo.organization;
|
|
case 'department-sea-pools':
|
|
return customer.managerdepartment === loginInfo.managerdepartment;
|
|
default:
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 过滤普通客户数据 - 优化版
|
|
filterCustomersByLoginInfo(customers, loginInfo, level) {
|
|
// 如果level为null,只进行基础的登录信息过滤(用于批量加载模式)
|
|
if (!level) {
|
|
// 基础过滤:只过滤不属于任何公海池的数据
|
|
return customers.filter(customer => {
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
// 只保留普通等级客户
|
|
return ['important', 'normal', 'low-value', 'logistics', 'unclassified'].includes(customerLevel);
|
|
});
|
|
}
|
|
|
|
// 针对特定等级的过滤(用于单独加载模式)
|
|
const filteredCustomers = customers.filter(customer => {
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
|
|
// 根据不同等级进行过滤
|
|
switch (level) {
|
|
case 'company-sea-pools':
|
|
return customerLevel === 'company-sea-pools';
|
|
case 'department-sea-pools':
|
|
return customerLevel === 'department-sea-pools' &&
|
|
customer.managerdepartment === loginInfo.managerdepartment;
|
|
case 'organization-sea-pools':
|
|
return customerLevel === 'organization-sea-pools' &&
|
|
customer.organization === loginInfo.organization;
|
|
case 'all':
|
|
// 全部客户:包含所有普通等级,不包含公海池
|
|
return ['important', 'normal', 'low-value', 'logistics', 'unclassified'].includes(customerLevel);
|
|
default:
|
|
// 其他客户等级:直接比较等级
|
|
return customerLevel === level;
|
|
}
|
|
});
|
|
|
|
// 移除不必要的日志,减少性能消耗
|
|
return filteredCustomers;
|
|
}
|
|
|
|
// 如果当前正在查看该等级,更新UI
|
|
updateUIIfActive(level, data) {
|
|
const currentActiveLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level');
|
|
|
|
if (currentActiveLevel === level) {
|
|
console.log(`🎯 立即更新UI: ${level}`);
|
|
optimizedProcessFilteredCustomers(data, level);
|
|
}
|
|
}
|
|
|
|
// 手动刷新缓存
|
|
async refreshCache(level = null) {
|
|
console.log('🔄 手动刷新缓存');
|
|
|
|
if (level) {
|
|
await this.refreshLevelData(level);
|
|
} else {
|
|
// 刷新所有等级
|
|
const levels = [
|
|
'important', 'normal', 'low-value', 'logistics', 'unclassified',
|
|
'company-sea-pools', 'organization-sea-pools', 'department-sea-pools', 'all'
|
|
];
|
|
|
|
for (const level of levels) {
|
|
await this.refreshLevelData(level);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 启动自动刷新
|
|
startAutoRefresh() {
|
|
if (this.updateInterval) {
|
|
clearInterval(this.updateInterval);
|
|
}
|
|
|
|
this.updateInterval = setInterval(() => {
|
|
console.log('🕒 自动刷新缓存');
|
|
this.refreshCache();
|
|
}, this.cacheTTL);
|
|
}
|
|
|
|
// 停止自动刷新
|
|
stopAutoRefresh() {
|
|
if (this.updateInterval) {
|
|
clearInterval(this.updateInterval);
|
|
this.updateInterval = null;
|
|
}
|
|
}
|
|
|
|
// 获取缓存状态
|
|
getCacheStatus() {
|
|
const status = {};
|
|
const now = Date.now();
|
|
|
|
for (const [level, timestamp] of this.timestamps) {
|
|
const data = this.cache.get(level);
|
|
const age = now - timestamp;
|
|
const isExpired = age > this.cacheTTL;
|
|
|
|
status[level] = {
|
|
dataCount: data ? data.length : 0,
|
|
age: Math.round(age / 1000), // 秒
|
|
isExpired,
|
|
lastUpdated: new Date(timestamp).toLocaleTimeString()
|
|
};
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// 清空缓存
|
|
clear() {
|
|
this.cache.clear();
|
|
this.timestamps.clear();
|
|
console.log('🧹 缓存已清空');
|
|
}
|
|
}
|
|
// 初始化缓存系统
|
|
window.customerCache = new CustomerDataCache();
|
|
|
|
// 页面加载时预加载所有等级数据
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
console.log('🚀 页面加载完成,开始预加载客户数据');
|
|
try {
|
|
// 并行预加载所有等级数据,提升性能
|
|
await window.customerCache.preloadAllLevels();
|
|
console.log('✅ 所有等级数据预加载完成');
|
|
} catch (error) {
|
|
console.error('❌ 预加载数据失败:', error);
|
|
}
|
|
});
|
|
|
|
|
|
function optimizedProcessFilteredCustomers(customers, level) {
|
|
// 快速检查空数据
|
|
if (!customers || customers.length === 0) {
|
|
return [];
|
|
}
|
|
|
|
// 直接获取目标等级
|
|
const targetLevels = getTargetLevelsForFilter(level);
|
|
|
|
// 优化:对于大多数情况,直接使用原始数据,避免创建新数组
|
|
let filteredCustomers = customers;
|
|
|
|
// 对于所有非'all'级别,包括公海池级别,都需要执行过滤逻辑
|
|
if (targetLevels.length > 0 && level !== 'all') {
|
|
const temp = [];
|
|
const len = customers.length;
|
|
for (let i = 0; i < len; i++) {
|
|
const customer = customers[i];
|
|
const standardizedLevel = standardizeCustomerLevel(customer.level);
|
|
|
|
if (targetLevels.includes(standardizedLevel)) {
|
|
// 避免创建新对象,直接使用原始对象
|
|
temp.push(customer);
|
|
}
|
|
}
|
|
filteredCustomers = temp;
|
|
console.log(`🧹 客户数据过滤完成:原始数量=${customers.length},过滤后数量=${filteredCustomers.length},目标等级=${level}`);
|
|
}
|
|
|
|
// 使用switch语句替代对象映射,减少查找开销
|
|
switch (level) {
|
|
case 'important':
|
|
importantCustomersData = filteredCustomers;
|
|
renderLevelTable('important', filteredCustomers);
|
|
break;
|
|
case 'normal':
|
|
normalCustomersData = filteredCustomers;
|
|
renderLevelTable('normal', filteredCustomers);
|
|
break;
|
|
case 'low-value':
|
|
lowCustomersData = filteredCustomers;
|
|
renderLevelTable('low', filteredCustomers);
|
|
break;
|
|
case 'logistics':
|
|
logisticsCustomersData = filteredCustomers;
|
|
renderLevelTable('logistics', filteredCustomers);
|
|
break;
|
|
case 'unclassified':
|
|
unclassifiedCustomersData = filteredCustomers;
|
|
renderLevelTable('unclassified', filteredCustomers);
|
|
break;
|
|
case 'company-sea-pools':
|
|
companySeaPoolsCustomersData = filteredCustomers;
|
|
renderLevelTable('company-sea-pools', filteredCustomers);
|
|
break;
|
|
case 'organization-sea-pools':
|
|
organizationSeaPoolsCustomersData = filteredCustomers;
|
|
renderLevelTable('organization-sea-pools', filteredCustomers);
|
|
break;
|
|
case 'department-sea-pools':
|
|
departmentSeaPoolsCustomersData = filteredCustomers;
|
|
renderLevelTable('department-sea-pools', filteredCustomers);
|
|
break;
|
|
case 'all':
|
|
allCustomersData = filteredCustomers;
|
|
renderAllCustomersTable();
|
|
updateAllCustomersPaginationInfo();
|
|
// 优化:减少不必要的函数调用
|
|
break;
|
|
}
|
|
|
|
// 返回处理后的数据,以便Promise可以正确解析
|
|
return filteredCustomers;
|
|
}
|
|
|
|
// 辅助函数:获取用于过滤的目标等级数组
|
|
function getTargetLevelsForFilter(level) {
|
|
const levelMap = {
|
|
'important': ['important'],
|
|
'normal': ['normal'],
|
|
'low-value': ['low-value'],
|
|
'logistics': ['logistics'],
|
|
'unclassified': ['unclassified'],
|
|
'company-sea-pools': ['company-sea-pools'],
|
|
'organization-sea-pools': ['organization-sea-pools'],
|
|
'department-sea-pools': ['department-sea-pools'],
|
|
'all': ['important', 'normal', 'low-value', 'logistics', 'unclassified']
|
|
};
|
|
return levelMap[level] || [];
|
|
}
|
|
|
|
// 从URL参数获取认证信息
|
|
function getAuthInfoFromURL() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
|
|
// 先从URL中获取认证信息
|
|
let authInfo = {
|
|
managerId: decodeURIComponent(urlParams.get('managerId') || ''),
|
|
managercompany: decodeURIComponent(urlParams.get('managercompany') || ''),
|
|
managerdepartment: decodeURIComponent(urlParams.get('managerdepartment') || ''),
|
|
organization: decodeURIComponent(urlParams.get('organization') || ''),
|
|
role: decodeURIComponent(urlParams.get('role') || ''),
|
|
userName: decodeURIComponent(urlParams.get('userName') || ''),
|
|
assistant: decodeURIComponent(urlParams.get('assistant') || ''),
|
|
token: urlParams.get('token') || '',
|
|
isSupplySide: urlParams.get('isSupplySide') === 'true'
|
|
};
|
|
|
|
// 如果URL中没有获取到完整的认证信息,从localStorage中获取
|
|
if (!authInfo.userName || !authInfo.managerId) {
|
|
console.log('URL中认证信息不完整,尝试从localStorage获取');
|
|
try {
|
|
const currentUser = localStorage.getItem('currentUser');
|
|
if (currentUser) {
|
|
const user = JSON.parse(currentUser);
|
|
authInfo = {
|
|
managerId: authInfo.managerId || user.managerId || '',
|
|
managercompany: authInfo.managercompany || user.managercompany || '',
|
|
managerdepartment: authInfo.managerdepartment || user.managerdepartment || '',
|
|
organization: authInfo.organization || user.organization || '',
|
|
role: authInfo.role || user.role || '',
|
|
userName: authInfo.userName || user.userName || '',
|
|
assistant: authInfo.assistant || user.assistant || '',
|
|
token: authInfo.token || localStorage.getItem('authToken') || '',
|
|
isSupplySide: urlParams.get('isSupplySide') === 'true'
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.error('从localStorage获取认证信息失败:', error);
|
|
}
|
|
}
|
|
|
|
console.log('=== 从URL参数获取认证信息 ===');
|
|
console.log('认证信息:', authInfo);
|
|
|
|
return authInfo;
|
|
}
|
|
// 添加URL参数辅助函数
|
|
function appendAuthParams(url) {
|
|
const authInfo = getAuthInfoFromURL();
|
|
const separator = url.includes('?') ? '&' : '?';
|
|
|
|
return url + separator +
|
|
`managerId=${encodeURIComponent(authInfo.managerId)}&` +
|
|
`company=${encodeURIComponent(authInfo.managercompany)}&` +
|
|
`department=${encodeURIComponent(authInfo.managerdepartment)}&` +
|
|
`organization=${encodeURIComponent(authInfo.organization)}&` +
|
|
`role=${encodeURIComponent(authInfo.role)}&` +
|
|
`userName=${encodeURIComponent(authInfo.userName)}&` +
|
|
`assistant=${encodeURIComponent(authInfo.assistant)}&` +
|
|
`token=${encodeURIComponent(authInfo.token)}`;
|
|
}
|
|
|
|
// 用户信息下拉菜单功能
|
|
function initUserInfoDropdown() {
|
|
const authInfo = getAuthInfoFromURL();
|
|
|
|
// 修改登录按钮
|
|
const loginButton = document.getElementById('loginButton');
|
|
if (loginButton && authInfo.userName) {
|
|
// 找到通知按钮
|
|
const notificationButton = document.getElementById('notificationButton');
|
|
|
|
// 创建用户信息容器
|
|
const userInfoContainer = document.createElement('div');
|
|
userInfoContainer.className = 'user-info-container';
|
|
|
|
// 创建用户信息按钮
|
|
const userInfoBtn = document.createElement('button');
|
|
userInfoBtn.className = 'user-info-btn';
|
|
userInfoBtn.innerHTML = `
|
|
<div class="user-avatar-large" style="width: 24px; height: 24px; font-size: 12px;">
|
|
<i class="fas fa-user"></i>
|
|
</div>
|
|
<span>${authInfo.userName}</span>
|
|
<i class="fas fa-chevron-down" style="font-size: 12px;"></i>
|
|
`;
|
|
|
|
// 创建下拉菜单
|
|
const userInfoDropdown = document.createElement('div');
|
|
userInfoDropdown.className = 'user-info-dropdown';
|
|
userInfoDropdown.innerHTML = `
|
|
<div class="user-info-header">
|
|
<div class="user-avatar-large">
|
|
<i class="fas fa-user"></i>
|
|
</div>
|
|
<div class="user-info-main">
|
|
<div class="user-name">${authInfo.userName || '用户'}</div>
|
|
<div class="user-role">${authInfo.role || '员工'}</div>
|
|
</div>
|
|
</div>
|
|
<div class="user-info-details">
|
|
<div class="user-info-item">
|
|
<span class="user-info-label">
|
|
<i class="fas fa-building"></i>
|
|
公司
|
|
</span>
|
|
<span class="user-info-value">${authInfo.managercompany || '-'}</span>
|
|
</div>
|
|
<div class="user-info-item">
|
|
<span class="user-info-label">
|
|
<i class="fas fa-users"></i>
|
|
部门
|
|
</span>
|
|
<span class="user-info-value">${authInfo.managerdepartment || '-'}</span>
|
|
</div>
|
|
<div class="user-info-item">
|
|
<span class="user-info-label">
|
|
<i class="fas fa-sitemap"></i>
|
|
组织
|
|
</span>
|
|
<span class="user-info-value">${authInfo.organization || '-'}</span>
|
|
</div>
|
|
<div class="user-info-item">
|
|
<span class="user-info-label">
|
|
<i class="fas fa-id-card"></i>
|
|
职位
|
|
</span>
|
|
<span class="user-info-value">${authInfo.role || '-'}</span>
|
|
</div>
|
|
<div class="user-info-item">
|
|
<span class="user-info-label">
|
|
<i class="fas fa-hands-helping"></i>
|
|
协助人
|
|
</span>
|
|
<span class="user-info-value">${authInfo.assistant || '-'}</span>
|
|
</div>
|
|
</div>
|
|
<button class="logout-btn" id="logoutBtn">
|
|
<i class="fas fa-sign-out-alt"></i> 退出登录
|
|
</button>
|
|
`;
|
|
|
|
// 创建包含通知按钮和用户信息按钮的容器
|
|
const headerActionsContainer = document.createElement('div');
|
|
headerActionsContainer.className = 'header-actions-container';
|
|
|
|
// 先添加通知按钮,再添加用户信息按钮和下拉菜单
|
|
headerActionsContainer.appendChild(notificationButton);
|
|
userInfoContainer.appendChild(userInfoBtn);
|
|
userInfoContainer.appendChild(userInfoDropdown);
|
|
headerActionsContainer.appendChild(userInfoContainer);
|
|
|
|
// 替换原来的登录按钮
|
|
loginButton.parentNode.replaceChild(headerActionsContainer, loginButton);
|
|
|
|
// 绑定显示/隐藏下拉菜单事件
|
|
userInfoBtn.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
userInfoDropdown.classList.toggle('active');
|
|
userInfoBtn.classList.toggle('active');
|
|
});
|
|
|
|
// 点击其他地方关闭下拉菜单
|
|
document.addEventListener('click', function () {
|
|
userInfoDropdown.classList.remove('active');
|
|
userInfoBtn.classList.remove('active');
|
|
});
|
|
|
|
// 阻止下拉菜单内部点击时关闭
|
|
userInfoDropdown.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
// 绑定退出登录事件
|
|
document.getElementById('logoutBtn').addEventListener('click', function () {
|
|
// 添加退出动画
|
|
this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 退出中...';
|
|
this.disabled = true;
|
|
|
|
// 模拟退出过程
|
|
setTimeout(() => {
|
|
window.location.href = 'loginmm.html';
|
|
}, 800);
|
|
});
|
|
}
|
|
}
|
|
|
|
// 在页面加载完成后初始化用户信息下拉菜单
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initUserInfoDropdown();
|
|
});
|
|
|
|
// 在页面加载完成后初始化用户信息下拉菜单
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
initUserInfoDropdown();
|
|
});
|
|
|
|
// 客户类型下拉框交互已集成到客户等级下拉框的交互逻辑中
|
|
// 无需单独的JavaScript代码,因为已使用相同的类名和结构
|
|
// 使用IIFE封装,避免全局变量污染
|
|
(function () {
|
|
// 基本需求多选下拉框交互
|
|
const needsSelectTrigger = document.getElementById('needs-select-trigger');
|
|
const needsSelectDropdown = document.getElementById('needs-select-dropdown');
|
|
const needsSelectOptions = document.querySelectorAll('.needs-select-option:not(.select-all)');
|
|
const allOptions = document.querySelectorAll('.needs-select-option');
|
|
const selectAllOption = document.querySelector('.needs-select-option.select-all');
|
|
const needsHiddenInput = document.getElementById('add-needs');
|
|
const placeholder = needsSelectTrigger.querySelector('.placeholder');
|
|
|
|
// 存储选中的值和文本
|
|
let selectedNeeds = {
|
|
values: [],
|
|
texts: []
|
|
};
|
|
|
|
// 最多选择数量限制
|
|
const MAX_SELECTIONS = 5;
|
|
|
|
// 初始化
|
|
function init() {
|
|
bindEvents();
|
|
}
|
|
|
|
// 绑定所有事件
|
|
function bindEvents() {
|
|
// 点击触发器切换下拉菜单显示状态
|
|
needsSelectTrigger.addEventListener('click', toggleDropdown);
|
|
|
|
// 点击选项选择/取消选择需求(多选逻辑)
|
|
allOptions.forEach(option => {
|
|
if (option.classList.contains('select-all')) {
|
|
option.addEventListener('click', handleSelectAll);
|
|
} else {
|
|
option.addEventListener('click', handleOptionSelect);
|
|
}
|
|
});
|
|
|
|
// 点击页面其他地方关闭下拉菜单
|
|
document.addEventListener('click', closeOnOutsideClick);
|
|
|
|
// 阻止下拉菜单内部点击事件冒泡
|
|
needsSelectDropdown.addEventListener('click', e => e.stopPropagation());
|
|
|
|
// 按ESC键关闭下拉菜单
|
|
document.addEventListener('keydown', handleEscKey);
|
|
|
|
// 支持键盘导航
|
|
document.addEventListener('keydown', handleKeyboardNavigation);
|
|
}
|
|
|
|
// 切换下拉菜单显示状态
|
|
function toggleDropdown(e) {
|
|
e.stopPropagation();
|
|
|
|
// 获取当前下拉框的当前状态
|
|
const isCurrentlyActive = needsSelectDropdown.classList.contains('active');
|
|
|
|
// 如果当前下拉框不是激活状态,则激活它
|
|
if (!isCurrentlyActive) {
|
|
// 关闭页面上所有其他类型的下拉框
|
|
// 关闭客户等级下拉框(只关闭显示,不重置值)
|
|
const levelSelectContainers = document.querySelectorAll('.level-select-container');
|
|
levelSelectContainers.forEach(container => {
|
|
const dropdown = container.querySelector('.level-select-dropdown');
|
|
const trigger = container.querySelector('.level-select-trigger');
|
|
if (dropdown && dropdown.classList.contains('active')) {
|
|
dropdown.classList.remove('active');
|
|
// 保留trigger的active状态,这样选中的值仍然可见
|
|
}
|
|
});
|
|
|
|
// 关闭页面大小选择下拉框
|
|
const pageSizeDropdowns = document.querySelectorAll('.custom-select-dropdown.active');
|
|
pageSizeDropdowns.forEach(dropdown => {
|
|
dropdown.classList.remove('active');
|
|
});
|
|
|
|
// 激活当前下拉框
|
|
needsSelectDropdown.classList.add('active');
|
|
needsSelectTrigger.classList.add('active');
|
|
updateSelectionLimitHint();
|
|
}
|
|
}
|
|
|
|
// 关闭下拉菜单
|
|
function closeDropdown() {
|
|
if (needsSelectDropdown.classList.contains('active')) {
|
|
needsSelectDropdown.classList.remove('active');
|
|
needsSelectTrigger.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// 点击页面外部关闭下拉菜单
|
|
function closeOnOutsideClick(e) {
|
|
// 只有在点击的目标不是下拉框触发器或下拉框本身时才关闭下拉框
|
|
if (!needsSelectTrigger.contains(e.target) && !needsSelectDropdown.contains(e.target)) {
|
|
closeDropdown();
|
|
}
|
|
}
|
|
|
|
// 处理ESC键关闭下拉菜单
|
|
function handleEscKey(e) {
|
|
if (e.key === 'Escape' && needsSelectDropdown.classList.contains('active')) {
|
|
closeDropdown();
|
|
}
|
|
}
|
|
|
|
// 处理选项选择
|
|
function handleOptionSelect(e) {
|
|
e.stopPropagation();
|
|
|
|
const value = this.getAttribute('data-value');
|
|
const text = this.textContent;
|
|
const index = selectedNeeds.values.indexOf(value);
|
|
|
|
// 切换选中状态
|
|
if (index === -1) {
|
|
// 检查是否已达到最大选择数量
|
|
if (selectedNeeds.values.length >= MAX_SELECTIONS) {
|
|
showSelectionLimitAlert();
|
|
return;
|
|
}
|
|
|
|
// 未选中,添加到选中数组
|
|
selectedNeeds.values.push(value);
|
|
selectedNeeds.texts.push(text);
|
|
this.classList.add('selected');
|
|
|
|
// 添加选中动画
|
|
this.classList.add('selecting');
|
|
setTimeout(() => this.classList.remove('selecting'), 300);
|
|
} else {
|
|
// 已选中,从数组中移除
|
|
selectedNeeds.values.splice(index, 1);
|
|
selectedNeeds.texts.splice(index, 1);
|
|
this.classList.remove('selected');
|
|
}
|
|
|
|
// 更新全选状态
|
|
updateSelectAllState();
|
|
|
|
// 更新显示文本和隐藏输入值
|
|
updateNeedsDisplay();
|
|
updateSelectionLimitHint();
|
|
}
|
|
|
|
// 处理全选/取消全选
|
|
function handleSelectAll(e) {
|
|
e.stopPropagation();
|
|
|
|
// 检查全选选项的当前状态
|
|
const isCurrentlySelected = selectAllOption.classList.contains('selected');
|
|
|
|
if (isCurrentlySelected) {
|
|
// 如果全选选项已被选中,二次点击时取消所有选择
|
|
clearAllSelections();
|
|
// 直接更新显示,不需要再调用updateSelectAllState,因为已经清除了所有状态
|
|
} else {
|
|
// 执行全选,考虑最大选择限制
|
|
selectAllPossible();
|
|
// 手动设置全选选项为选中状态
|
|
selectAllOption.classList.add('selected');
|
|
}
|
|
|
|
// 更新显示和隐藏输入值
|
|
updateNeedsDisplay();
|
|
updateSelectionLimitHint();
|
|
}
|
|
|
|
// 检查是否所有选项都已选中
|
|
function checkIfAllSelected() {
|
|
return Array.from(needsSelectOptions).every(option =>
|
|
option.classList.contains('selected')
|
|
);
|
|
}
|
|
|
|
// 清除所有选择
|
|
function clearAllSelections() {
|
|
selectedNeeds.values = [];
|
|
selectedNeeds.texts = [];
|
|
|
|
// 清除所有选项的选中状态
|
|
needsSelectOptions.forEach(option => {
|
|
option.classList.remove('selected');
|
|
});
|
|
|
|
// 特别清除全选选项自身的选中状态
|
|
selectAllOption.classList.remove('selected');
|
|
}
|
|
|
|
// 全选所有可能的选项(考虑最大选择限制)
|
|
function selectAllPossible() {
|
|
// 先清空现有选择
|
|
clearAllSelections();
|
|
|
|
// 计算可以选择的最大数量
|
|
const selectableCount = Math.min(needsSelectOptions.length, MAX_SELECTIONS);
|
|
|
|
// 选择前N个选项
|
|
Array.from(needsSelectOptions).slice(0, selectableCount).forEach(option => {
|
|
const value = option.getAttribute('data-value');
|
|
const text = option.textContent;
|
|
|
|
selectedNeeds.values.push(value);
|
|
selectedNeeds.texts.push(text);
|
|
option.classList.add('selected');
|
|
});
|
|
}
|
|
|
|
// 更新全选选项的状态
|
|
function updateSelectAllState() {
|
|
const allSelected = checkIfAllSelected();
|
|
|
|
if (allSelected) {
|
|
selectAllOption.classList.add('selected');
|
|
} else {
|
|
selectAllOption.classList.remove('selected');
|
|
}
|
|
}
|
|
|
|
// 更新显示的选中项(使用标签样式)
|
|
function updateNeedsDisplay() {
|
|
// 移除所有现有标签(保留隐藏输入框和占位符)
|
|
const existingTags = needsSelectTrigger.querySelectorAll('.selected-tag');
|
|
existingTags.forEach(tag => tag.remove());
|
|
|
|
// 更新占位符显示状态
|
|
if (selectedNeeds.values.length === 0) {
|
|
placeholder.style.display = 'block';
|
|
} else {
|
|
placeholder.style.display = 'none';
|
|
|
|
// 添加新标签
|
|
selectedNeeds.texts.forEach((text, index) => {
|
|
const tag = document.createElement('div');
|
|
tag.className = 'selected-tag';
|
|
tag.innerHTML = `
|
|
${text}
|
|
<span class="remove-tag" data-index="${index}">×</span>
|
|
`;
|
|
needsSelectTrigger.insertBefore(tag, needsHiddenInput);
|
|
|
|
// 添加删除标签事件
|
|
tag.querySelector('.remove-tag').addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
removeTag(index);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 更新隐藏输入框值 - 同时保存值和文本
|
|
needsHiddenInput.value = selectedNeeds.texts.join(','); // 保存中文文本而不是data-value
|
|
}
|
|
|
|
// 移除标签
|
|
function removeTag(index) {
|
|
// 从数组中移除
|
|
const removedValue = selectedNeeds.values.splice(index, 1)[0];
|
|
selectedNeeds.texts.splice(index, 1);
|
|
|
|
// 更新选项的选中状态
|
|
needsSelectOptions.forEach(option => {
|
|
if (option.getAttribute('data-value') === removedValue) {
|
|
option.classList.remove('selected');
|
|
}
|
|
});
|
|
|
|
// 更新全选状态
|
|
updateSelectAllState();
|
|
|
|
// 更新显示
|
|
updateNeedsDisplay();
|
|
updateSelectionLimitHint();
|
|
}
|
|
|
|
// 更新选择限制提示
|
|
function updateSelectionLimitHint() {
|
|
// 移除现有提示
|
|
const existingHint = needsSelectDropdown.querySelector('.selection-limit');
|
|
if (existingHint) {
|
|
existingHint.remove();
|
|
}
|
|
|
|
// 当达到选择上限时显示提示
|
|
if (selectedNeeds.values.length >= MAX_SELECTIONS) {
|
|
const hint = document.createElement('div');
|
|
hint.className = 'selection-limit';
|
|
hint.textContent = `最多只能选择${MAX_SELECTIONS}项`;
|
|
needsSelectDropdown.appendChild(hint);
|
|
}
|
|
}
|
|
|
|
// 显示选择限制弹窗提示
|
|
function showSelectionLimitAlert() {
|
|
// 创建一个临时提示元素
|
|
const alert = document.createElement('div');
|
|
alert.style.position = 'fixed';
|
|
alert.style.top = '20px';
|
|
alert.style.left = '50%';
|
|
alert.style.transform = 'translateX(-50%)';
|
|
alert.style.backgroundColor = 'rgba(231, 76, 60, 0.9)';
|
|
alert.style.color = 'white';
|
|
alert.style.padding = '10px 20px';
|
|
alert.style.borderRadius = '4px';
|
|
alert.style.zIndex = '9999';
|
|
alert.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
|
alert.style.transition = 'all 0.3s ease';
|
|
alert.textContent = `最多只能选择${MAX_SELECTIONS}项`;
|
|
|
|
document.body.appendChild(alert);
|
|
|
|
// 3秒后移除提示
|
|
setTimeout(() => {
|
|
alert.style.opacity = '0';
|
|
alert.style.transform = 'translateX(-50%) translateY(-20px)';
|
|
setTimeout(() => alert.remove(), 300);
|
|
}, 2000);
|
|
}
|
|
|
|
// 键盘导航支持
|
|
function handleKeyboardNavigation(e) {
|
|
// 只有下拉菜单打开时才处理键盘导航
|
|
if (!needsSelectDropdown.classList.contains('active')) return;
|
|
|
|
const options = Array.from(allOptions);
|
|
const currentIndex = options.findIndex(option => option === document.activeElement);
|
|
|
|
// 向下箭头
|
|
if (e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % options.length;
|
|
options[nextIndex].focus();
|
|
}
|
|
|
|
// 向上箭头
|
|
if (e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
const prevIndex = currentIndex === -1 ? options.length - 1 : (currentIndex - 1 + options.length) % options.length;
|
|
options[prevIndex].focus();
|
|
}
|
|
|
|
// 回车键或空格键选择
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
if (currentIndex !== -1) {
|
|
options[currentIndex].click();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 初始化组件
|
|
init();
|
|
|
|
// 暴露一些公共方法供外部调用(如果需要)
|
|
window.needsSelect = {
|
|
getSelectedValues: () => [...selectedNeeds.values],
|
|
setSelectedValues: (values) => {
|
|
// 清空现有选择
|
|
clearAllSelections();
|
|
|
|
// 设置新选择
|
|
values.forEach(value => {
|
|
const option = Array.from(needsSelectOptions).find(
|
|
opt => opt.getAttribute('data-value') === value
|
|
);
|
|
|
|
if (option && selectedNeeds.values.length < MAX_SELECTIONS) {
|
|
const text = option.textContent;
|
|
selectedNeeds.values.push(value);
|
|
selectedNeeds.texts.push(text);
|
|
option.classList.add('selected');
|
|
}
|
|
});
|
|
|
|
updateSelectAllState();
|
|
updateNeedsDisplay();
|
|
updateSelectionLimitHint();
|
|
},
|
|
clearSelections: () => {
|
|
clearAllSelections();
|
|
updateSelectAllState();
|
|
updateNeedsDisplay();
|
|
updateSelectionLimitHint();
|
|
}
|
|
};
|
|
})();
|
|
|
|
// 客户等级和客户类型选择器 - 支持多个下拉框
|
|
(function () {
|
|
// 获取所有的选择器容器
|
|
const containers = document.querySelectorAll('.level-select-container');
|
|
|
|
// 为每个容器初始化
|
|
containers.forEach(container => {
|
|
const trigger = container.querySelector('.level-select-trigger');
|
|
const dropdown = container.querySelector('.level-select-dropdown');
|
|
const options = container.querySelectorAll('.level-select-option');
|
|
const hiddenInput = trigger.querySelector('input[type="hidden"]');
|
|
const placeholder = trigger.querySelector('.placeholder');
|
|
|
|
// 存储选中的值和文本
|
|
let selectedValue = '';
|
|
let selectedText = '';
|
|
|
|
// 初始化
|
|
function init() {
|
|
bindEvents();
|
|
}
|
|
|
|
// 绑定所有事件
|
|
function bindEvents() {
|
|
// 点击触发器切换下拉菜单显示状态
|
|
trigger.addEventListener('click', toggleDropdown);
|
|
|
|
// 点击选项选择(单选逻辑)
|
|
options.forEach(option => {
|
|
option.addEventListener('click', handleOptionSelect);
|
|
});
|
|
|
|
// 点击页面其他地方关闭下拉菜单
|
|
document.addEventListener('click', closeOnOutsideClick);
|
|
|
|
// 阻止下拉菜单内部点击事件冒泡
|
|
dropdown.addEventListener('click', e => e.stopPropagation());
|
|
|
|
// 按ESC键关闭下拉菜单
|
|
document.addEventListener('keydown', handleEscKey);
|
|
}
|
|
|
|
// 切换下拉菜单显示状态
|
|
function toggleDropdown(e) {
|
|
e.stopPropagation();
|
|
|
|
// 获取当前下拉框的当前状态
|
|
const isCurrentlyActive = dropdown.classList.contains('active');
|
|
|
|
// 如果当前下拉框不是激活状态,则先关闭其他所有下拉框,再激活当前下拉框
|
|
if (!isCurrentlyActive) {
|
|
// 关闭其他所有客户等级下拉菜单
|
|
containers.forEach(otherContainer => {
|
|
if (otherContainer !== container) {
|
|
const otherDropdown = otherContainer.querySelector('.level-select-dropdown');
|
|
const otherTrigger = otherContainer.querySelector('.level-select-trigger');
|
|
if (otherDropdown && otherDropdown.classList.contains('active')) {
|
|
otherDropdown.classList.remove('active');
|
|
if (otherTrigger) otherTrigger.classList.remove('active');
|
|
}
|
|
}
|
|
});
|
|
|
|
// 关闭基本需求多选下拉框
|
|
const needsSelectDropdown = document.querySelector('.needs-select-dropdown');
|
|
const needsSelectTrigger = document.querySelector('.needs-select-trigger');
|
|
if (needsSelectDropdown && needsSelectDropdown.classList.contains('active')) {
|
|
needsSelectDropdown.classList.remove('active');
|
|
if (needsSelectTrigger) needsSelectTrigger.classList.remove('active');
|
|
}
|
|
|
|
// 关闭页面大小选择下拉框
|
|
const pageSizeDropdowns = document.querySelectorAll('.custom-select-dropdown.active');
|
|
pageSizeDropdowns.forEach(dropdown => {
|
|
dropdown.classList.remove('active');
|
|
});
|
|
|
|
// 激活当前下拉框
|
|
dropdown.classList.add('active');
|
|
trigger.classList.add('active');
|
|
}
|
|
}
|
|
|
|
// 关闭下拉菜单
|
|
function closeDropdown() {
|
|
if (dropdown && dropdown.classList.contains('active')) {
|
|
dropdown.classList.remove('active');
|
|
if (trigger) trigger.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
// 点击页面外部关闭下拉菜单
|
|
function closeOnOutsideClick(e) {
|
|
// 检查点击目标是否在当前容器外部,并且不是任何其他下拉框容器
|
|
const isOutsideAllContainers = Array.from(containers).every(c => !c.contains(e.target));
|
|
|
|
// 检查点击目标是否不是基本需求下拉框
|
|
const isNotNeedsSelect = !document.getElementById('needs-select-container')?.contains(e.target);
|
|
|
|
// 检查点击目标是否不是分页大小选择器
|
|
const isNotPageSizeSelect = !document.querySelector('.page-size-selector')?.contains(e.target);
|
|
|
|
// 只有当点击完全在所有下拉框外部时才关闭
|
|
if (isOutsideAllContainers && isNotNeedsSelect && isNotPageSizeSelect) {
|
|
closeDropdown();
|
|
}
|
|
}
|
|
|
|
// 处理ESC键关闭下拉菜单
|
|
function handleEscKey(e) {
|
|
if (e.key === 'Escape' && dropdown.classList.contains('active')) {
|
|
closeDropdown();
|
|
}
|
|
}
|
|
|
|
// 处理选项选择(单选逻辑)
|
|
function handleOptionSelect() {
|
|
// 移除所有选项的选中状态
|
|
options.forEach(option => option.classList.remove('selected'));
|
|
|
|
// 设置当前选项为选中状态
|
|
this.classList.add('selected');
|
|
|
|
// 更新选中值和文本
|
|
selectedValue = this.getAttribute('data-value');
|
|
selectedText = this.textContent;
|
|
|
|
// 更新显示和隐藏输入值
|
|
updateDisplay();
|
|
|
|
// 选择后关闭下拉框
|
|
closeDropdown();
|
|
}
|
|
|
|
// 更新显示的选中项
|
|
function updateDisplay() {
|
|
// 移除现有选中文本
|
|
const existingText = trigger.querySelector('.selected-text');
|
|
if (existingText) {
|
|
existingText.remove();
|
|
}
|
|
|
|
// 更新占位符显示状态
|
|
if (selectedValue) {
|
|
placeholder.style.display = 'none';
|
|
|
|
// 创建选中文本元素并应用对应等级的样式类
|
|
const textElement = document.createElement('div');
|
|
textElement.className = `selected-text ${selectedValue}`;
|
|
textElement.textContent = selectedText;
|
|
trigger.insertBefore(textElement, hiddenInput);
|
|
} else {
|
|
placeholder.style.display = 'block';
|
|
}
|
|
|
|
// 更新隐藏输入框值
|
|
hiddenInput.value = selectedValue;
|
|
}
|
|
|
|
// 初始化组件
|
|
init();
|
|
});
|
|
})();
|
|
|
|
// 1. 创建电话号码校验弹窗(动态插入DOM)
|
|
function createPhoneCheckModal() {
|
|
if (document.getElementById('phoneCheckModal')) return;
|
|
|
|
const modalHTML = `
|
|
<div id="phoneCheckModal" class="modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); justify-content: center; align-items: center; z-index: 1000;">
|
|
<div class="modal-content" style="background: white; padding: 20px; border-radius: 5px; width: 300px;">
|
|
<div class="modal-header">
|
|
<h3>电话号码校验</h3>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p id="phoneCheckMsg">正在校验电话号码是否重复...</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button id="phoneCheckConfirm" class="btn" style="padding: 5px 15px; cursor: pointer;">确认</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.body.insertAdjacentHTML('beforeend', modalHTML);
|
|
|
|
document.getElementById('phoneCheckConfirm').addEventListener('click', function () {
|
|
document.getElementById('phoneCheckModal').style.display = 'none';
|
|
});
|
|
}
|
|
|
|
// 2. 电话号码格式校验(辅助函数)
|
|
function isValidPhoneNumber(phoneNumber) {
|
|
// 验证是否为11位数字
|
|
const phoneRegex = /^1\d{10}$/;
|
|
return phoneRegex.test(phoneNumber.trim());
|
|
}
|
|
|
|
// 修改检查登录状态的函数
|
|
function checkLoginStatus() {
|
|
const urlAuthInfo = getAuthInfoFromURL();
|
|
|
|
// 检查URL参数中是否有必要的认证信息
|
|
if (urlAuthInfo.userName && urlAuthInfo.userName !== '') {
|
|
console.log('✅ URL参数中有认证信息,允许访问');
|
|
return true;
|
|
}
|
|
|
|
|
|
// 如果都没有认证信息,跳转到登录页面
|
|
console.log('❌ 未找到认证信息,跳转到登录页面');
|
|
window.location.href = 'loginmm.html';
|
|
return false;
|
|
}
|
|
|
|
|
|
// 3. 发送后端请求校验电话号码是否重复
|
|
async function checkPhoneDuplicate(phoneNumber) {
|
|
if (!isValidPhoneNumber(phoneNumber)) {
|
|
const modal = document.getElementById('phoneCheckModal') || (createPhoneCheckModal(), document.getElementById('phoneCheckModal'));
|
|
modal.style.display = 'flex';
|
|
showPhoneCheckResult('请输入有效的11位手机号码', false);
|
|
return false;
|
|
}
|
|
const modal = document.getElementById('phoneCheckModal');
|
|
if (!modal) {
|
|
createPhoneCheckModal(); // 确保弹窗已创建
|
|
}
|
|
modal.style.display = 'flex';
|
|
document.getElementById('phoneCheckMsg').textContent = '正在校验电话号码是否重复...';
|
|
|
|
try {
|
|
// 修改这行:添加认证参数
|
|
const url = appendAuthParams(`${API_BASE_URL}/pool/customers/check-phone?phoneNumber=${encodeURIComponent(phoneNumber.trim())}`);
|
|
const response = await fetch(url, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) { // 现在response已正确定义
|
|
throw new Error(`请求失败: ${response.status}`);
|
|
}
|
|
|
|
// 处理后端响应(保持后续逻辑不变)
|
|
const result = await response.json().catch(() => {
|
|
return { success: false, message: '服务器响应格式错误' };
|
|
});
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.message || '校验失败');
|
|
}
|
|
|
|
// 正确代码
|
|
if (result.isDuplicate) { // 直接从根对象获取
|
|
showPhoneCheckResult(`电话号码 ${phoneNumber} 已存在,请更换其他号码`, false);
|
|
return false;
|
|
} else {
|
|
showPhoneCheckResult(`电话号码 ${phoneNumber} 可用`, true);
|
|
return true;
|
|
}
|
|
} catch (error) {
|
|
console.error('电话号码校验错误:', error);
|
|
showPhoneCheckResult(`校验失败:${error.message}`, false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// 4. 显示电话号码校验结果
|
|
function showPhoneCheckResult(msg, isSuccess) {
|
|
const msgElement = document.getElementById('phoneCheckMsg');
|
|
const confirmBtn = document.getElementById('phoneCheckConfirm');
|
|
|
|
msgElement.textContent = msg;
|
|
msgElement.style.color = isSuccess ? '#67c23a' : '#f56c6c'; // 成功绿色,失败红色
|
|
|
|
if (isSuccess) {
|
|
setTimeout(() => {
|
|
document.getElementById('phoneCheckModal').style.display = 'none'; // 2秒后自动关闭
|
|
}, 2000);
|
|
} else {
|
|
confirmBtn.focus(); // 失败时聚焦确认按钮,方便用户关闭后修改
|
|
}
|
|
}
|
|
|
|
|
|
// 5. 初始化电话号码校验功能(页面加载时执行)
|
|
function initPhoneCheck() {
|
|
console.log('进入initPhoneCheck函数'); // 新增日志
|
|
|
|
// 确保弹窗创建函数可用
|
|
if (typeof createPhoneCheckModal !== 'function') {
|
|
console.error('createPhoneCheckModal函数未定义');
|
|
return; // 提前退出
|
|
}
|
|
|
|
createPhoneCheckModal();
|
|
|
|
// 查找电话输入框(兼容动态生成的元素)
|
|
const phoneInput = document.getElementById('add-phone');
|
|
|
|
if (!phoneInput) {
|
|
console.error('未找到id为add-phone的输入框');
|
|
// 尝试延迟查找(针对动态生成的元素)
|
|
setTimeout(() => {
|
|
const delayedInput = document.getElementById('add-phone');
|
|
if (delayedInput) {
|
|
console.log('延迟找到电话输入框,绑定事件');
|
|
bindPhoneEvents(delayedInput);
|
|
} else {
|
|
console.error('延迟查找仍未找到add-phone输入框');
|
|
}
|
|
}, 1000); // 1秒后重试
|
|
return;
|
|
}
|
|
|
|
// 绑定事件处理函数
|
|
bindPhoneEvents(phoneInput);
|
|
}
|
|
|
|
// 独立的事件绑定函数(便于复用)
|
|
function bindPhoneEvents(inputElement) {
|
|
// 失去焦点时校验
|
|
inputElement.addEventListener('blur', async () => {
|
|
console.log('电话输入框失去焦点,准备校验');
|
|
await checkPhoneDuplicate(inputElement.value);
|
|
});
|
|
|
|
// 按Enter时校验
|
|
inputElement.addEventListener('keydown', async (e) => {
|
|
if (e.key === 'Enter') {
|
|
console.log('按下Enter键,准备校验');
|
|
await checkPhoneDuplicate(inputElement.value);
|
|
}
|
|
});
|
|
|
|
console.log('电话输入框事件绑定完成');
|
|
}
|
|
|
|
|
|
|
|
function initAllCustomersPagination() {
|
|
const prevButton = document.getElementById('all-customers-prev-page');
|
|
const nextButton = document.getElementById('all-customers-next-page');
|
|
const pageInput = document.getElementById('all-customers-page-input');
|
|
const goButton = document.getElementById('all-customers-page-go-btn');
|
|
|
|
// 修复:确保第一页数据正确显示
|
|
if (allCustomersData.length > 0) {
|
|
allCustomersCurrentPage = 1; // 强制重置到第一页
|
|
renderAllCustomersTable();
|
|
updateAllCustomersPaginationInfo();
|
|
}
|
|
|
|
if (prevButton) {
|
|
prevButton.addEventListener('click', function () {
|
|
if (allCustomersCurrentPage > 1) {
|
|
allCustomersCurrentPage--;
|
|
renderAllCustomersTable();
|
|
updateAllCustomersPaginationInfo();
|
|
// 滚动到表格顶部
|
|
const tableContainer = document.querySelector('.data-table-container');
|
|
if (tableContainer) tableContainer.scrollTop = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (nextButton) {
|
|
nextButton.addEventListener('click', function () {
|
|
const totalPages = Math.ceil(allCustomersData.length / allCustomersPageSize);
|
|
if (allCustomersCurrentPage < totalPages) {
|
|
allCustomersCurrentPage++;
|
|
renderAllCustomersTable();
|
|
updateAllCustomersPaginationInfo();
|
|
// 滚动到表格顶部
|
|
const tableContainer = document.querySelector('.data-table-container');
|
|
if (tableContainer) tableContainer.scrollTop = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (goButton && pageInput) {
|
|
goButton.addEventListener('click', function () {
|
|
const pageNum = parseInt(pageInput.value);
|
|
const totalPages = Math.ceil(allCustomersData.length / allCustomersPageSize);
|
|
|
|
if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= totalPages) {
|
|
allCustomersCurrentPage = pageNum;
|
|
renderAllCustomersTable();
|
|
updateAllCustomersPaginationInfo();
|
|
// 滚动到表格顶部
|
|
const tableContainer = document.querySelector('.data-table-container');
|
|
if (tableContainer) tableContainer.scrollTop = 0;
|
|
} else {
|
|
alert(`请输入1到${totalPages}之间的页码`);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
function initEditButton() {
|
|
console.log("初始化编辑按钮");
|
|
const editButton = document.getElementById('editCustomerBtn');
|
|
|
|
if (editButton) {
|
|
// 移除所有现有的事件监听器
|
|
const newEditButton = editButton.cloneNode(true);
|
|
editButton.parentNode.replaceChild(newEditButton, editButton);
|
|
|
|
// 重新绑定事件
|
|
document.getElementById('editCustomerBtn').addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
console.log("编辑按钮被点击");
|
|
toggleEditMode();
|
|
});
|
|
}
|
|
console.log("编辑按钮初始化完成");
|
|
}
|
|
|
|
// 在显示客户详情模态框时调用
|
|
function showCustomerModal() {
|
|
document.getElementById('customerModal').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// 重新初始化编辑按钮
|
|
initEditButton();
|
|
}
|
|
// 修改最近客户表格的渲染函数
|
|
function renderRecentCustomers(customers) {
|
|
console.log('渲染最近客户表格,输入数据数量:', customers?.length || 0);
|
|
|
|
const recentTable = document.getElementById('recent-customers-table');
|
|
const recentTbody = recentTable ? recentTable.querySelector('tbody') : null;
|
|
|
|
if (!recentTbody) {
|
|
console.error('最近客户表格的tbody元素未找到');
|
|
return;
|
|
}
|
|
|
|
console.log('更新最近客户显示,DOM元素检查成功');
|
|
recentTbody.innerHTML = ''; // 清空现有内容
|
|
|
|
if (!customers || !Array.isArray(customers) || customers.length === 0) {
|
|
console.log('最近客户没有数据,显示空状态');
|
|
recentTbody.innerHTML = '<tr><td colspan="6" style="text-align: center; color: #666;">暂无客户数据</td></tr>';
|
|
return;
|
|
}
|
|
|
|
console.log('找到最近客户数据,开始渲染:', customers.length, '条记录');
|
|
|
|
// 使用模板字符串一次性生成表格行,提高性能
|
|
let rowHTML = '';
|
|
customers.forEach((customer, index) => {
|
|
// 确保客户数据有效
|
|
if (!customer) {
|
|
console.warn(`第${index+1}条客户数据为空,跳过渲染`);
|
|
return;
|
|
}
|
|
|
|
// 增强字段获取,支持多种可能的字段名
|
|
const customerId = customer.id || customer.userId || '';
|
|
const phone = customer.phoneNumber || customer.phone || '';
|
|
const company = customer.company || customer.name || '微信客户';
|
|
const nickName = customer.nickName || customer.userName || customer.contact || '-';
|
|
const wechat = customer.wechat || '-';
|
|
|
|
console.log(`渲染最近客户 #${index + 1}:`, company, phone, customerId);
|
|
|
|
// 使用模板字符串构建表格行
|
|
rowHTML += `
|
|
<tr data-id="${customerId || phone}" data-phone="${phone}" class="customer-row recent-customer">
|
|
<td>${company}</td>
|
|
<td style="display: flex; flex-direction: column;">
|
|
<div>${nickName}</div>
|
|
<div style="font-size: 12px; color: #666;">微信: ${wechat}</div>
|
|
</td>
|
|
<td>${phone}</td>
|
|
<td class="last-order-cell"></td> <!-- 最后订单(暂未实现) -->
|
|
<td class="status-cell">
|
|
<div class="status active">活跃</div>
|
|
</td>
|
|
<td>
|
|
<button class="action-btn follow-up-btn" data-id="${customerId}" data-phone="${phone}">
|
|
<i class="fas fa-tasks"></i> 跟进
|
|
</button>
|
|
</td>
|
|
</tr>`;
|
|
});
|
|
|
|
// 一次性设置所有行,提高性能
|
|
console.log('设置最近客户表格内容,生成行数:', customers.length);
|
|
recentTbody.innerHTML = rowHTML;
|
|
|
|
// 绑定事件 - 使用委托方式更高效
|
|
// 先移除之前的事件监听器,避免重复绑定
|
|
const oldHandler = recentTable._tableClickHandler;
|
|
if (oldHandler) {
|
|
recentTable.removeEventListener('click', oldHandler);
|
|
}
|
|
|
|
// 创建新的处理函数并绑定
|
|
const tableClickHandler = (e) => {
|
|
const followBtn = e.target.closest('.follow-up-btn');
|
|
const detailBtn = e.target.closest('.view-details');
|
|
const row = e.target.closest('tr.customer-row');
|
|
|
|
if (followBtn) {
|
|
e.stopPropagation();
|
|
const customerId = followBtn.getAttribute('data-id');
|
|
const phone = followBtn.getAttribute('data-phone');
|
|
console.log('跟进按钮被点击:', { customerId, phone });
|
|
// 调用跟进功能
|
|
if (typeof showFollowUpInterface === 'function') {
|
|
showFollowUpInterface(customerId, phone);
|
|
} else if (typeof viewCustomerDetails === 'function') {
|
|
viewCustomerDetails(customerId, phone);
|
|
} else {
|
|
console.warn('跟进相关函数未找到');
|
|
}
|
|
}
|
|
else if (detailBtn) {
|
|
e.stopPropagation();
|
|
const customerId = detailBtn.getAttribute('data-id');
|
|
const phone = detailBtn.getAttribute('data-phone');
|
|
console.log('查看详情按钮被点击:', { customerId, phone });
|
|
// 调用查看详情功能
|
|
if (typeof viewCustomerDetails === 'function') {
|
|
viewCustomerDetails(customerId, phone);
|
|
}
|
|
}
|
|
else if (row) {
|
|
const customerId = row.getAttribute('data-id');
|
|
const phone = row.getAttribute('data-phone');
|
|
console.log('客户行被点击:', { customerId, phone });
|
|
// 调用查看详情功能
|
|
if (typeof viewCustomerDetails === 'function') {
|
|
viewCustomerDetails(customerId, phone);
|
|
}
|
|
}
|
|
};
|
|
|
|
// 存储处理函数引用以便后续移除
|
|
recentTable._tableClickHandler = tableClickHandler;
|
|
recentTable.addEventListener('click', tableClickHandler);
|
|
|
|
// 确保表格可见
|
|
recentTable.style.display = 'table';
|
|
console.log('最近客户表格渲染完成');
|
|
}
|
|
|
|
|
|
// 生成8位随机数的客户ID
|
|
function generateCustomerId() {
|
|
const timestamp = Date.now().toString().slice(-6);
|
|
const randomNum = Math.floor(1000 + Math.random() * 9000);
|
|
return `CUST-${timestamp}${randomNum}`;
|
|
}
|
|
|
|
// 实时显示北京时间
|
|
function updateBeijingTime() {
|
|
// 创建北京时区的日期对象
|
|
const now = new Date();
|
|
const beijingTime = new Date(now.getTime() + 8 * 60 * 60 * 1000); // UTC+8
|
|
|
|
// 获取年月日
|
|
const year = beijingTime.getUTCFullYear();
|
|
const month = beijingTime.getUTCMonth() + 1; // 月份从0开始,所以+1
|
|
const day = beijingTime.getUTCDate();
|
|
|
|
// 获取星期几(0是星期日)
|
|
const weekdays = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
|
|
const weekday = weekdays[beijingTime.getUTCDay()];
|
|
|
|
// 实时更新时分秒
|
|
const hours = String(beijingTime.getUTCHours()).padStart(2, '0');
|
|
const minutes = String(beijingTime.getUTCMinutes()).padStart(2, '0');
|
|
const seconds = String(beijingTime.getUTCSeconds()).padStart(2, '0');
|
|
|
|
const timeString = `${month}/${day}/${weekday},${hours}:${minutes}:${seconds}`;
|
|
document.getElementById('beijingTime').textContent = timeString;
|
|
}
|
|
|
|
updateBeijingTime();
|
|
setInterval(updateBeijingTime, 1000);
|
|
|
|
// // 登录按钮事件
|
|
// document.getElementById('loginButton').addEventListener('click', function () {
|
|
// alert('登录功能即将实现');
|
|
// });
|
|
|
|
// 页面切换功能
|
|
const navItems = document.querySelectorAll('.nav-item');
|
|
const pages = document.querySelectorAll('.page');
|
|
const sidebar = document.querySelector('.sidebar');
|
|
|
|
const dashboardSidebarWidth = sidebar.offsetWidth;
|
|
const dashboardSidebarHeight = sidebar.offsetHeight;
|
|
|
|
navItems.forEach(item => {
|
|
item.addEventListener('click', function () {
|
|
navItems.forEach(nav => nav.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
const pageId = this.getAttribute('data-page') + '-page';
|
|
|
|
pages.forEach(page => page.classList.remove('active'));
|
|
document.getElementById(pageId).classList.add('active');
|
|
|
|
sidebar.style.width = dashboardSidebarWidth + 'px';
|
|
sidebar.style.minWidth = dashboardSidebarWidth + 'px';
|
|
sidebar.style.maxWidth = dashboardSidebarWidth + 'px';
|
|
sidebar.style.minHeight = dashboardSidebarHeight + 'px';
|
|
|
|
if (pageId === 'customers-page') {
|
|
safeUpdatePagination();
|
|
}
|
|
});
|
|
});
|
|
|
|
// 分页功能实现
|
|
let PAGE_SIZE = 50;
|
|
let currentPage = 1;
|
|
let totalPages = 1;
|
|
let currentLevel = 'important';
|
|
let currentSearchTerm = '';
|
|
let showAll = false;
|
|
|
|
|
|
// ============ 添加安全分页更新函数 ============
|
|
// 优化分页更新逻辑,避免频繁重渲染
|
|
function safeUpdatePagination() {
|
|
if (isUpdatingPagination) {
|
|
pendingPaginationUpdate = true;
|
|
return;
|
|
}
|
|
|
|
isUpdatingPagination = true;
|
|
|
|
// 使用防抖技术,避免频繁更新
|
|
clearTimeout(window.paginationUpdateTimeout);
|
|
window.paginationUpdateTimeout = setTimeout(() => {
|
|
updatePagination();
|
|
|
|
setTimeout(() => {
|
|
isUpdatingPagination = false;
|
|
if (pendingPaginationUpdate) {
|
|
pendingPaginationUpdate = false;
|
|
safeUpdatePagination();
|
|
}
|
|
}, 50);
|
|
}, 100); // 增加延迟,减少频繁更新
|
|
}
|
|
|
|
function validatePaginationData() {
|
|
const issues = [];
|
|
|
|
if (!document.getElementById('customers-table')) {
|
|
issues.push('客户表格不存在');
|
|
}
|
|
|
|
if (currentLevel !== 'all') {
|
|
const tbody = document.getElementById(`${currentLevel}-customers`);
|
|
if (!tbody) {
|
|
issues.push(`${currentLevel} 表格不存在`);
|
|
}
|
|
}
|
|
|
|
if (PAGE_SIZE <= 0) {
|
|
issues.push('PAGE_SIZE 无效');
|
|
}
|
|
if (currentPage < 1) {
|
|
issues.push('currentPage 无效');
|
|
}
|
|
|
|
if (issues.length > 0) {
|
|
console.warn('分页数据完整性检查失败:', issues);
|
|
PAGE_SIZE = Math.max(1, PAGE_SIZE);
|
|
currentPage = Math.max(1, currentPage);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
// ===========================================
|
|
|
|
|
|
// 初始化分页
|
|
function initPagination() {
|
|
safeUpdatePagination();
|
|
|
|
document.getElementById('prev-page').addEventListener('click', function () {
|
|
if (currentPage > 1) {
|
|
currentPage--;
|
|
safeUpdatePagination();
|
|
scrollToTableTop();
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page').addEventListener('click', function () {
|
|
if (currentPage < totalPages) {
|
|
currentPage++;
|
|
safeUpdatePagination();
|
|
scrollToTableTop();
|
|
}
|
|
});
|
|
|
|
const pageInput = document.getElementById('page-input');
|
|
const goButton = document.getElementById('page-go-btn');
|
|
|
|
goButton.addEventListener('click', function () {
|
|
goToPage();
|
|
});
|
|
|
|
pageInput.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToPage();
|
|
}
|
|
});
|
|
|
|
// 客户等级切换功能
|
|
const levelTabs = document.querySelectorAll('.level-tab');
|
|
levelTabs.forEach(tab => {
|
|
tab.addEventListener('click', function () {
|
|
levelTabs.forEach(t => t.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
const level = this.getAttribute('data-level');
|
|
|
|
// 隐藏所有表格
|
|
document.getElementById('important-customers').style.display = 'none';
|
|
document.getElementById('normal-customers').style.display = 'none';
|
|
document.getElementById('low-customers').style.display = 'none';
|
|
document.getElementById('logistics-customers').style.display = 'none';
|
|
document.getElementById('unclassified-customers').style.display = 'none';
|
|
document.getElementById('public-sea-customers').style.display = 'none';
|
|
document.getElementById('company-sea-pools-customers').style.display = 'none';
|
|
document.getElementById('organization-sea-pools-customers').style.display = 'none';
|
|
document.getElementById('department-sea-pools-customers').style.display = 'none';
|
|
|
|
// 显示/隐藏分页控件
|
|
const defaultPagination = document.getElementById('customers-pagination');
|
|
const allCustomersPagination = document.getElementById('all-customers-pagination');
|
|
|
|
if (level === 'all') {
|
|
// 显示全部客户分页,隐藏默认分页
|
|
if (defaultPagination) defaultPagination.style.display = 'none';
|
|
if (allCustomersPagination) allCustomersPagination.style.display = 'flex';
|
|
|
|
// 修复:确保全部客户数据正确刷新并立即显示
|
|
setTimeout(() => {
|
|
refreshCustomerData('all');
|
|
// 强制重新渲染表格
|
|
renderAllCustomersTable();
|
|
updateAllCustomersPaginationInfo();
|
|
}, 100);
|
|
} else {
|
|
// 显示默认分页,隐藏全部客户分页
|
|
if (defaultPagination) defaultPagination.style.display = 'flex';
|
|
if (allCustomersPagination) allCustomersPagination.style.display = 'none';
|
|
|
|
// 显示对应等级的表格并请求数据
|
|
document.getElementById(`${level}-customers`).style.display = 'table-row-group';
|
|
const levelMap = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'low': 'low-value',
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified',
|
|
'public-sea': 'public-sea',
|
|
'company-sea-pools': 'company-sea-pools',
|
|
'organization-sea-pools': 'organization-sea-pools',
|
|
'department-sea-pools': 'department-sea-pools'
|
|
};
|
|
|
|
// 如果切换到公司公海池,应用默认的今天时间筛选
|
|
if (level === 'company-sea-pools' || level === 'department-sea-pools' || level === 'organization-sea-pools') {
|
|
initializeDefaultFilter();
|
|
|
|
// 手动触发应用筛选
|
|
const now = new Date();
|
|
const beijingNow = new Date(now.getTime() + (8 * 60 * 60 * 1000));
|
|
const startOfToday = new Date(beijingNow.getFullYear(), beijingNow.getMonth(), beijingNow.getDate(), 0, 0, 0);
|
|
const endOfToday = new Date(beijingNow.getFullYear(), beijingNow.getMonth(), beijingNow.getDate(), 23, 59, 59, 999);
|
|
|
|
// 应用时间筛选
|
|
applyTimeFilter(startOfToday, endOfToday);
|
|
}
|
|
|
|
refreshCustomerData(levelMap[level] || level);
|
|
}
|
|
|
|
currentLevel = level;
|
|
currentPage = 1;
|
|
safeUpdatePagination();
|
|
});
|
|
});
|
|
|
|
const pageSizeDisplay = document.getElementById('page-size-display');
|
|
const pageSizeDropdown = document.getElementById('page-size-dropdown');
|
|
const pageSizeOptions = document.querySelectorAll('#page-size-dropdown .custom-select-option');
|
|
|
|
pageSizeDisplay.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
|
|
// 获取当前下拉框的当前状态
|
|
const isCurrentlyActive = pageSizeDropdown.classList.contains('active');
|
|
|
|
// 关闭其他所有类型的下拉框
|
|
// 关闭基本需求多选下拉框
|
|
const needsSelectDropdown = document.querySelector('.needs-select-dropdown');
|
|
const needsSelectTrigger = document.querySelector('.needs-select-trigger');
|
|
if (needsSelectDropdown && needsSelectDropdown.classList.contains('active')) {
|
|
needsSelectDropdown.classList.remove('active');
|
|
if (needsSelectTrigger) needsSelectTrigger.classList.remove('active');
|
|
}
|
|
|
|
// 关闭客户等级下拉框
|
|
const levelSelectContainers = document.querySelectorAll('.level-select-container');
|
|
levelSelectContainers.forEach(container => {
|
|
const dropdown = container.querySelector('.level-select-dropdown');
|
|
const trigger = container.querySelector('.level-select-trigger');
|
|
if (dropdown && dropdown.classList.contains('active')) {
|
|
dropdown.classList.remove('active');
|
|
if (trigger) trigger.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// 关闭跟进记录的页面大小选择下拉框
|
|
const followUpPageSizeDropdown = document.getElementById('follow-up-page-size-dropdown');
|
|
if (followUpPageSizeDropdown && followUpPageSizeDropdown !== pageSizeDropdown && followUpPageSizeDropdown.classList.contains('active')) {
|
|
followUpPageSizeDropdown.classList.remove('active');
|
|
}
|
|
|
|
// 如果当前下拉框不是激活状态,则激活它(避免关闭后立即重新打开)
|
|
if (!isCurrentlyActive) {
|
|
pageSizeDropdown.classList.add('active');
|
|
}
|
|
});
|
|
|
|
pageSizeOptions.forEach(option => {
|
|
option.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
|
|
pageSizeOptions.forEach(opt => opt.classList.remove('selected'));
|
|
this.classList.add('selected');
|
|
|
|
const selectedValue = this.getAttribute('data-value');
|
|
pageSizeDisplay.textContent = this.textContent;
|
|
|
|
showAll = selectedValue === 'all';
|
|
PAGE_SIZE = showAll ? Infinity : parseInt(selectedValue, 10);
|
|
currentPage = 1;
|
|
safeUpdatePagination();
|
|
scrollToTableTop();
|
|
|
|
pageSizeDropdown.classList.remove('active');
|
|
});
|
|
});
|
|
|
|
document.addEventListener('click', function (e) {
|
|
// 只有在点击的目标不是下拉框触发器或选项时才关闭下拉框
|
|
if (!pageSizeDisplay.contains(e.target) && !pageSizeDropdown.contains(e.target)) {
|
|
pageSizeDropdown.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
const customersSearch = document.getElementById('customers-search');
|
|
customersSearch.addEventListener('input', function () {
|
|
currentSearchTerm = this.value.toLowerCase().trim();
|
|
currentPage = 1;
|
|
safeUpdatePagination();
|
|
});
|
|
|
|
const dashboardSearch = document.getElementById('dashboard-search');
|
|
dashboardSearch.addEventListener('input', function () {
|
|
const searchTerm = this.value.toLowerCase().trim();
|
|
filterRecentCustomers(searchTerm);
|
|
});
|
|
|
|
// 初始化跟进页面的搜索功能
|
|
const followUpSearch = document.getElementById('follow-up-search');
|
|
followUpSearch.addEventListener('input', function () {
|
|
const searchTerm = this.value.toLowerCase().trim();
|
|
filterFollowUpRecords(searchTerm);
|
|
});
|
|
|
|
// 初始化跟进页面分页
|
|
initFollowUpPagination();
|
|
}
|
|
|
|
function goToPage() {
|
|
const pageInput = document.getElementById('page-input');
|
|
const pageNum = parseInt(pageInput.value, 10);
|
|
|
|
if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= totalPages) {
|
|
currentPage = pageNum;
|
|
safeUpdatePagination();
|
|
scrollToTableTop();
|
|
} else {
|
|
pageInput.value = currentPage;
|
|
alert(`请输入1到${totalPages}之间的页码`);
|
|
}
|
|
}
|
|
|
|
function updateTotalInfo(totalCount, currentPageCount) {
|
|
const totalInfoElement = document.getElementById('total-info');
|
|
if (totalInfoElement) {
|
|
totalInfoElement.textContent = `当前页面: ${currentPageCount}条 / 总计: ${totalCount}条`;
|
|
}
|
|
}
|
|
|
|
|
|
function updatePagination() {
|
|
// 数据完整性检查
|
|
if (!validatePaginationData()) {
|
|
console.log('分页数据无效,已尝试修复');
|
|
}
|
|
|
|
console.log('=== 分页状态调试 ===');
|
|
console.log('currentLevel:', currentLevel);
|
|
console.log('currentPage:', currentPage);
|
|
console.log('PAGE_SIZE:', PAGE_SIZE);
|
|
console.log('showAll:', showAll);
|
|
|
|
let allRecords = [];
|
|
const validLevels = ['important', 'normal', 'low', 'logistics', 'unclassified'];
|
|
|
|
if (currentLevel === 'all') {
|
|
validLevels.forEach(level => {
|
|
const tbody = document.getElementById(`${level}-customers`);
|
|
if (tbody) {
|
|
const records = Array.from(tbody.querySelectorAll('tr'));
|
|
allRecords = allRecords.concat(records);
|
|
}
|
|
});
|
|
} else {
|
|
const tbody = document.getElementById(`${currentLevel}-customers`);
|
|
if (tbody) {
|
|
allRecords = Array.from(tbody.querySelectorAll('tr'));
|
|
}
|
|
}
|
|
|
|
console.log('获取到的记录数量:', allRecords.length);
|
|
|
|
const filteredRecords = currentSearchTerm
|
|
? allRecords.filter(record => {
|
|
const text = record.textContent.toLowerCase();
|
|
return text.includes(currentSearchTerm);
|
|
})
|
|
: allRecords;
|
|
|
|
console.log('过滤后记录数量:', filteredRecords.length);
|
|
|
|
const totalItems = filteredRecords.length;
|
|
totalPages = showAll ? 1 : Math.max(1, Math.ceil(totalItems / PAGE_SIZE));
|
|
|
|
currentPage = Math.min(Math.max(1, currentPage), totalPages);
|
|
|
|
displayCurrentPageRecords(filteredRecords);
|
|
|
|
document.getElementById('page-info').textContent = `第 ${currentPage} 页,共 ${totalPages} 页`;
|
|
document.getElementById('page-input').value = currentPage;
|
|
|
|
document.getElementById('prev-page').disabled = currentPage === 1 || showAll;
|
|
document.getElementById('next-page').disabled = currentPage === totalPages || showAll;
|
|
|
|
updateTotalInfo(totalItems, filteredRecords.length);
|
|
}
|
|
|
|
function displayCurrentPageRecords(filteredRecords) {
|
|
const allRecords = document.querySelectorAll('#customers-table tbody tr');
|
|
allRecords.forEach(record => {
|
|
record.style.display = 'none';
|
|
});
|
|
|
|
const startIndex = showAll ? 0 : (currentPage - 1) * PAGE_SIZE;
|
|
const endIndex = showAll ? filteredRecords.length : startIndex + PAGE_SIZE;
|
|
const currentPageRecords = filteredRecords.slice(startIndex, endIndex);
|
|
|
|
currentPageRecords.forEach(record => {
|
|
record.style.display = '';
|
|
});
|
|
|
|
return currentPageRecords;
|
|
}
|
|
|
|
function filterRecentCustomers(searchTerm) {
|
|
const records = document.querySelectorAll('#recent-customers-table tbody tr');
|
|
|
|
records.forEach(record => {
|
|
const text = record.textContent.toLowerCase();
|
|
const shouldShow = !searchTerm || text.includes(searchTerm);
|
|
record.style.display = shouldShow ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
|
|
// 过滤跟进记录
|
|
function filterFollowUpRecords(searchTerm) {
|
|
const records = document.querySelectorAll('#follow-up-table tbody tr');
|
|
|
|
records.forEach(record => {
|
|
const text = record.textContent.toLowerCase();
|
|
const shouldShow = !searchTerm || text.includes(searchTerm);
|
|
record.style.display = shouldShow ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
function scrollToTableTop() {
|
|
const tableContainer = document.querySelector('.data-table-container');
|
|
if (tableContainer) {
|
|
tableContainer.scrollTop = 0;
|
|
}
|
|
}
|
|
|
|
// 客户等级切换功能
|
|
const levelTabs = document.querySelectorAll('.level-tab');
|
|
levelTabs.forEach(tab => {
|
|
tab.addEventListener('click', function () {
|
|
// 先停止任何进行中的更新
|
|
isUpdatingPagination = false;
|
|
pendingPaginationUpdate = false;
|
|
|
|
levelTabs.forEach(t => t.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
const level = this.getAttribute('data-level');
|
|
currentLevel = level;
|
|
currentPage = 1;
|
|
|
|
console.log('切换等级到:', level);
|
|
|
|
setTimeout(() => {
|
|
safeUpdatePagination(); // 改为调用安全更新
|
|
}, 100);
|
|
});
|
|
});
|
|
|
|
// 跟进记录数据和分页
|
|
let followUpPAGE_SIZE = 50;
|
|
let followUpCurrentPage = 1;
|
|
let followUpTotalPages = 1;
|
|
let followUpShowAll = false;
|
|
let followUpCurrentSearchTerm = '';
|
|
|
|
// 初始化跟进分页
|
|
function initFollowUpPagination() {
|
|
updateFollowUpPagination();
|
|
|
|
document.getElementById('follow-up-prev-page').addEventListener('click', function () {
|
|
if (followUpCurrentPage > 1) {
|
|
followUpCurrentPage--;
|
|
updateFollowUpPagination();
|
|
scrollToTableTop();
|
|
}
|
|
});
|
|
|
|
document.getElementById('follow-up-next-page').addEventListener('click', function () {
|
|
if (followUpCurrentPage < followUpTotalPages) {
|
|
followUpCurrentPage++;
|
|
updateFollowUpPagination();
|
|
scrollToTableTop();
|
|
}
|
|
});
|
|
|
|
const pageInput = document.getElementById('follow-up-page-input');
|
|
const goButton = document.getElementById('follow-up-page-go-btn');
|
|
|
|
goButton.addEventListener('click', function () {
|
|
goToFollowUpPage();
|
|
});
|
|
|
|
pageInput.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToFollowUpPage();
|
|
}
|
|
});
|
|
|
|
const pageSizeDisplay = document.getElementById('follow-up-page-size-display');
|
|
const pageSizeDropdown = document.getElementById('follow-up-page-size-dropdown');
|
|
const pageSizeOptions = document.querySelectorAll('#follow-up-page-size-dropdown .custom-select-option');
|
|
|
|
pageSizeDisplay.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
|
|
// 获取当前下拉框的当前状态
|
|
const isCurrentlyActive = pageSizeDropdown.classList.contains('active');
|
|
|
|
// 关闭其他所有类型的下拉框
|
|
// 关闭基本需求多选下拉框
|
|
const needsSelectDropdown = document.querySelector('.needs-select-dropdown');
|
|
const needsSelectTrigger = document.querySelector('.needs-select-trigger');
|
|
if (needsSelectDropdown && needsSelectDropdown.classList.contains('active')) {
|
|
needsSelectDropdown.classList.remove('active');
|
|
if (needsSelectTrigger) needsSelectTrigger.classList.remove('active');
|
|
}
|
|
|
|
// 关闭客户等级下拉框
|
|
const levelSelectContainers = document.querySelectorAll('.level-select-container');
|
|
levelSelectContainers.forEach(container => {
|
|
const dropdown = container.querySelector('.level-select-dropdown');
|
|
const trigger = container.querySelector('.level-select-trigger');
|
|
if (dropdown && dropdown.classList.contains('active')) {
|
|
dropdown.classList.remove('active');
|
|
if (trigger) trigger.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
// 关闭客户列表的页面大小选择下拉框
|
|
const customerPageSizeDropdown = document.getElementById('page-size-dropdown');
|
|
if (customerPageSizeDropdown && customerPageSizeDropdown !== pageSizeDropdown && customerPageSizeDropdown.classList.contains('active')) {
|
|
customerPageSizeDropdown.classList.remove('active');
|
|
}
|
|
|
|
// 如果当前下拉框不是激活状态,则激活它(避免关闭后立即重新打开)
|
|
if (!isCurrentlyActive) {
|
|
pageSizeDropdown.classList.add('active');
|
|
}
|
|
});
|
|
|
|
pageSizeOptions.forEach(option => {
|
|
option.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
|
|
pageSizeOptions.forEach(opt => opt.classList.remove('selected'));
|
|
this.classList.add('selected');
|
|
|
|
const selectedValue = this.getAttribute('data-value');
|
|
pageSizeDisplay.textContent = this.textContent;
|
|
|
|
followUpShowAll = selectedValue === 'all';
|
|
followUpPAGE_SIZE = followUpShowAll ? Infinity : parseInt(selectedValue, 10);
|
|
followUpCurrentPage = 1;
|
|
updateFollowUpPagination();
|
|
scrollToTableTop();
|
|
|
|
pageSizeDropdown.classList.remove('active');
|
|
});
|
|
});
|
|
|
|
document.addEventListener('click', function (e) {
|
|
// 只有在点击的目标不是下拉框触发器或选项时才关闭下拉框
|
|
if (!pageSizeDisplay.contains(e.target) && !pageSizeDropdown.contains(e.target)) {
|
|
pageSizeDropdown.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
const followUpSearch = document.getElementById('follow-up-search');
|
|
followUpSearch.addEventListener('input', function () {
|
|
followUpCurrentSearchTerm = this.value.toLowerCase().trim();
|
|
followUpCurrentPage = 1;
|
|
updateFollowUpPagination();
|
|
});
|
|
}
|
|
// 公海需求管理功能
|
|
class PublicSeaDemandManager {
|
|
constructor() {
|
|
this.currentProductId = null;
|
|
this.productItems = [];
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
console.log('初始化公海需求管理器');
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
// 绑定查看完整需求按钮
|
|
document.getElementById('viewFullDemandsBtn')?.addEventListener('click', () => {
|
|
this.openDemandsModal();
|
|
});
|
|
|
|
// 绑定关闭弹窗按钮
|
|
document.getElementById('closePublicSeaDemandsModal')?.addEventListener('click', () => {
|
|
this.closeDemandsModal();
|
|
});
|
|
|
|
// 点击弹窗外部关闭
|
|
document.getElementById('publicSeaDemandsModal')?.addEventListener('click', (e) => {
|
|
if (e.target === document.getElementById('publicSeaDemandsModal')) {
|
|
this.closeDemandsModal();
|
|
}
|
|
});
|
|
}
|
|
// 🔥 新增:根据后端返回的选中ID初始化选中状态
|
|
initializeSelectedDemand(customer) {
|
|
console.log('🎯 初始化选中需求 - 采购端', {
|
|
targetProductItemId: customer.targetProductItemId,
|
|
productItemsCount: this.productItems ? this.productItems.length : 0,
|
|
productItems: this.productItems
|
|
});
|
|
|
|
if (!this.productItems || this.productItems.length === 0) {
|
|
console.log('⚠️ 没有产品项数据,跳过初始化');
|
|
return;
|
|
}
|
|
|
|
// 🔥 关键修复:优先使用后端返回的选中产品项ID
|
|
if (customer.targetProductItemId) {
|
|
const selectedDemand = this.productItems.find(item =>
|
|
item.productId === customer.targetProductItemId
|
|
);
|
|
|
|
if (selectedDemand) {
|
|
console.log('✅ 根据后端选中ID设置需求:', selectedDemand.productId);
|
|
this.selectDemand(selectedDemand, this.getDemandIndex(selectedDemand));
|
|
return;
|
|
} else {
|
|
console.warn('⚠️ 后端返回的选中ID未找到对应产品项:', customer.targetProductItemId);
|
|
}
|
|
}
|
|
|
|
// 🔥 关键修复:如果没有后端选中ID或未找到,使用第一个产品项
|
|
console.log('🔄 使用第一个产品项作为默认选中');
|
|
this.selectDemand(this.productItems[0], 0);
|
|
}
|
|
|
|
// 获取产品项索引
|
|
getDemandIndex(demand) {
|
|
return this.productItems.findIndex(item =>
|
|
item.productId === demand.productId
|
|
);
|
|
}
|
|
|
|
// 🔥 新增:确保当前需求被选中
|
|
ensureCurrentDemandSelected() {
|
|
console.log('🎯 确保当前需求被选中 - 采购端');
|
|
|
|
// 如果当前没有选中任何需求,但有需求数据,则选中第一个
|
|
if (!this.currentProductId && this.productItems.length > 0) {
|
|
console.log('🔄 当前没有选中需求,自动选中第一个需求');
|
|
const firstDemand = this.productItems[0];
|
|
this.selectDemand(firstDemand, 0);
|
|
} else if (this.currentProductId) {
|
|
console.log('✅ 当前已有选中需求:', this.currentProductId);
|
|
} else {
|
|
console.log('⚠️ 没有可用的需求数据');
|
|
}
|
|
}
|
|
|
|
|
|
// 处理客户详情显示
|
|
handleCustomerDetails(customer) {
|
|
console.log('🔍 处理客户公海需求显示 - 采购端', {
|
|
customerId: customer.id,
|
|
hasProductItems: !!(customer.productItems),
|
|
productItemsType: typeof customer.productItems,
|
|
productItemsLength: customer.productItems ? customer.productItems.length : 0,
|
|
productItems: customer.productItems
|
|
});
|
|
|
|
// 🔥 关键修复:严格检查productItems的有效性
|
|
if (!customer.productItems ||
|
|
!Array.isArray(customer.productItems) ||
|
|
customer.productItems.length === 0 ||
|
|
customer.productItems.some(item => !item || !item.productId)) {
|
|
console.log('⚠️ 客户没有有效的公海需求数据,隐藏需求显示');
|
|
this.hideMultipleDemandsButton();
|
|
// 🔥 额外保险:清除可能的需求显示
|
|
this.clearDemandDisplay();
|
|
return;
|
|
}
|
|
|
|
// 🔥 关键修复:过滤有效的产品项
|
|
const validProductItems = customer.productItems.filter(item =>
|
|
item &&
|
|
item.productId &&
|
|
(item.productName || item.specification || item.quantity || item.grossWeight || item.yolk)
|
|
);
|
|
|
|
if (validProductItems.length === 0) {
|
|
console.log('⚠️ 过滤后没有有效的公海需求数据');
|
|
this.hideMultipleDemandsButton();
|
|
this.clearDemandDisplay();
|
|
return;
|
|
}
|
|
|
|
this.productItems = validProductItems;
|
|
console.log(`✅ 客户有 ${this.productItems.length} 个有效的公海需求`, this.productItems);
|
|
|
|
// 🔥 关键修改:根据后端数据初始化选中状态
|
|
this.initializeSelectedDemand(customer);
|
|
|
|
if (this.productItems.length > 1) {
|
|
this.showMultipleDemandsButton();
|
|
} else {
|
|
this.hideMultipleDemandsButton();
|
|
}
|
|
}
|
|
|
|
// 🔥 新增:清除需求显示
|
|
clearDemandDisplay() {
|
|
console.log('🧹 清除公海需求显示');
|
|
|
|
const demandFields = [
|
|
'detail-product-name', 'detail-variety', 'detail-specification',
|
|
'detail-quantity', 'detail-weight', 'detail-yolk'
|
|
];
|
|
|
|
demandFields.forEach(fieldId => {
|
|
const element = document.getElementById(fieldId);
|
|
if (element) {
|
|
element.textContent = '-';
|
|
}
|
|
const inputElement = document.getElementById(fieldId + '-input');
|
|
if (inputElement) {
|
|
inputElement.value = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
// 显示单个需求
|
|
showDemand(demand) {
|
|
console.log('显示公海需求 - 采购端:', demand);
|
|
|
|
this.currentProductId = demand.productId;
|
|
this.currentProductItem = demand;
|
|
this.updateDemandDisplay(demand);
|
|
this.setHiddenProductId(this.currentProductId);
|
|
}
|
|
|
|
// 获取产品项更新数据
|
|
getProductItemUpdateData() {
|
|
if (!this.currentProductItem) {
|
|
console.warn('没有当前产品项数据');
|
|
return null;
|
|
}
|
|
|
|
const updateData = {
|
|
productId: this.currentProductId,
|
|
productName: document.getElementById('detail-product-name-input')?.value || this.currentProductItem.productName || '',
|
|
variety: document.getElementById('detail-variety-input')?.value || this.currentProductItem.variety || '',
|
|
specification: document.getElementById('detail-specification-input')?.value || this.currentProductItem.specification || '',
|
|
quantity: parseInt(document.getElementById('detail-quantity-input')?.value) || this.currentProductItem.quantity || 0,
|
|
grossWeight: document.getElementById('detail-weight-input')?.value || this.currentProductItem.grossWeight || '',
|
|
yolk: document.getElementById('detail-yolk-input')?.value || this.currentProductItem.yolk || ''
|
|
};
|
|
|
|
console.log('产品项更新数据:', updateData);
|
|
return updateData;
|
|
}
|
|
|
|
// 获取精确更新所需的所有字段
|
|
getPreciseUpdateFields() {
|
|
const productId = this.getCurrentProductId();
|
|
const updateData = this.getProductItemUpdateData();
|
|
|
|
if (!productId || !updateData) {
|
|
console.warn('无法获取精确更新字段');
|
|
return {};
|
|
}
|
|
|
|
return {
|
|
targetProductItemId: productId,
|
|
updateProductItemData: updateData
|
|
};
|
|
}
|
|
|
|
// 更新需求显示
|
|
updateDemandDisplay(demand) {
|
|
console.log('🔄 更新需求显示 - 采购端:', demand);
|
|
|
|
const fields = {
|
|
'detail-product-name': demand.productName,
|
|
'detail-variety': demand.variety || '-',
|
|
'detail-specification': demand.specification,
|
|
'detail-quantity': demand.quantity,
|
|
'detail-weight': demand.grossWeight,
|
|
'detail-yolk': demand.yolk
|
|
};
|
|
|
|
Object.entries(fields).forEach(([id, value]) => {
|
|
const element = document.getElementById(id);
|
|
const inputElement = document.getElementById(id + '-input');
|
|
|
|
if (element) {
|
|
element.textContent = value || '-';
|
|
}
|
|
if (inputElement) {
|
|
inputElement.value = value || '';
|
|
}
|
|
});
|
|
|
|
console.log('✅ 公海需求显示已更新 - 采购端');
|
|
}
|
|
|
|
// 设置隐藏的productId
|
|
setHiddenProductId(productId) {
|
|
window.currentProductId = productId;
|
|
console.log('设置当前productId:', productId);
|
|
}
|
|
|
|
// 显示多个需求按钮
|
|
showMultipleDemandsButton() {
|
|
const btnContainer = document.getElementById('multipleDemandsBtn');
|
|
if (btnContainer) {
|
|
btnContainer.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
// 隐藏多个需求按钮
|
|
hideMultipleDemandsButton() {
|
|
const btnContainer = document.getElementById('multipleDemandsBtn');
|
|
if (btnContainer) {
|
|
btnContainer.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
// 打开需求选择弹窗
|
|
openDemandsModal() {
|
|
console.log('打开公海需求选择弹窗 - 采购端');
|
|
this.renderDemandCards();
|
|
document.getElementById('publicSeaDemandsModal').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
// 关闭需求选择弹窗
|
|
closeDemandsModal() {
|
|
console.log('关闭公海需求选择弹窗 - 采购端');
|
|
document.getElementById('publicSeaDemandsModal').classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
}
|
|
|
|
// 渲染需求卡片
|
|
renderDemandCards() {
|
|
const container = document.getElementById('publicSeaDemandsContainer');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = '';
|
|
|
|
this.productItems.forEach((demand, index) => {
|
|
const card = this.createDemandCard(demand, index);
|
|
container.appendChild(card);
|
|
});
|
|
|
|
console.log(`渲染了 ${this.productItems.length} 个需求卡片 - 采购端`);
|
|
}
|
|
|
|
// 创建需求卡片
|
|
createDemandCard(demand, index) {
|
|
const card = document.createElement('div');
|
|
card.className = 'demand-card';
|
|
const currentId = demand.productId;
|
|
|
|
// 🔥 关键修改:根据当前选中状态设置选中样式
|
|
if (currentId === this.currentProductId) {
|
|
card.classList.add('selected');
|
|
}
|
|
|
|
card.innerHTML = `
|
|
<div class="demand-card-header">
|
|
<div class="demand-card-title">公海需求</div>
|
|
<div class="demand-card-badge">需求${index + 1}</div>
|
|
</div>
|
|
<div class="demand-card-content">
|
|
<div class="demand-card-item">
|
|
<span class="demand-card-label">产品名称:</span>
|
|
<span class="demand-card-value">${demand.productName || '-'}</span>
|
|
</div>
|
|
<div class="demand-card-item">
|
|
<span class="demand-card-label">规格:</span>
|
|
<span class="demand-card-value">${demand.specification || '-'}</span>
|
|
</div>
|
|
<div class="demand-card-item">
|
|
<span class="demand-card-label">件数:</span>
|
|
<span class="demand-card-value">${demand.quantity || '-'}</span>
|
|
</div>
|
|
<div class="demand-card-item">
|
|
<span class="demand-card-label">毛重:</span>
|
|
<span class="demand-card-value">${demand.grossWeight || '-'}</span>
|
|
</div>
|
|
<div class="demand-card-item">
|
|
<span class="demand-card-label">蛋黄:</span>
|
|
<span class="demand-card-value">${demand.yolk || '-'}</span>
|
|
</div>
|
|
<div class="demand-card-item">
|
|
<span class="demand-card-label">需求ID:</span>
|
|
<span class="demand-card-value" style="font-size: 12px; color: #999;">${currentId || '-'}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
card.addEventListener('click', () => {
|
|
this.selectDemand(demand, index);
|
|
});
|
|
|
|
return card;
|
|
}
|
|
|
|
// 在PublicSeaDemandManager类中修改selectDemand方法
|
|
selectDemand(demand, index) {
|
|
console.log(`🎯 选择需求 ${index + 1} - 采购端:`, demand);
|
|
|
|
this.currentProductId = demand.productId;
|
|
this.currentProductItem = demand;
|
|
this.updateDemandDisplay(demand);
|
|
this.setHiddenProductId(this.currentProductId);
|
|
|
|
// 🔥 关键修复:重新调用viewCustomerDetails以刷新数据
|
|
if (window.currentCustomerData) {
|
|
const customerId = window.currentCustomerData.id;
|
|
const phoneNumber = window.currentCustomerData.phoneNumber;
|
|
|
|
console.log('🔄 重新加载客户详情以更新选中状态', {
|
|
customerId,
|
|
phoneNumber,
|
|
targetProductItemId: this.currentProductId
|
|
});
|
|
|
|
// 重新调用viewCustomerDetails,传递选中的产品项ID
|
|
viewCustomerDetails(customerId, phoneNumber, this.currentProductId);
|
|
}
|
|
|
|
console.log('✅ 需求选择完成,当前ID:', this.currentProductId);
|
|
this.closeDemandsModal();
|
|
this.showSelectionSuccessMessage(index + 1);
|
|
}
|
|
|
|
// 更新URL参数
|
|
updateUrlWithSelectedDemand(productId) {
|
|
const currentUrl = new URL(window.location.href);
|
|
currentUrl.searchParams.set('targetProductItemId', productId);
|
|
window.history.replaceState({}, '', currentUrl);
|
|
}
|
|
|
|
// 显示选择成功消息
|
|
showSelectionSuccessMessage(index) {
|
|
const message = document.createElement('div');
|
|
message.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: #4CAF50;
|
|
color: white;
|
|
padding: 10px 15px;
|
|
border-radius: 5px;
|
|
z-index: 10000;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
`;
|
|
message.textContent = `已选择需求 ${index}`;
|
|
document.body.appendChild(message);
|
|
|
|
setTimeout(() => {
|
|
document.body.removeChild(message);
|
|
}, 2000);
|
|
}
|
|
|
|
// 获取当前选中的productId
|
|
getCurrentProductId() {
|
|
return this.currentProductId;
|
|
}
|
|
|
|
// 重置状态
|
|
reset() {
|
|
console.log('🔄 重置公海需求管理器状态 - 采购端');
|
|
|
|
// 🔥 关键修复:完全清除所有状态
|
|
this.currentProductId = null;
|
|
this.currentProductItem = null;
|
|
this.productItems = [];
|
|
|
|
// 隐藏多个需求按钮
|
|
this.hideMultipleDemandsButton();
|
|
|
|
// 🔥 额外保险:清除全局状态
|
|
window.currentProductId = null;
|
|
|
|
// 🔥 额外保险:清除所有需求相关的UI显示
|
|
const demandFields = [
|
|
'detail-product-name', 'detail-variety', 'detail-specification',
|
|
'detail-quantity', 'detail-weight', 'detail-yolk'
|
|
];
|
|
demandFields.forEach(fieldId => {
|
|
const element = document.getElementById(fieldId);
|
|
if (element) {
|
|
element.textContent = '-';
|
|
}
|
|
const inputElement = document.getElementById(fieldId + '-input');
|
|
if (inputElement) {
|
|
inputElement.value = '';
|
|
}
|
|
});
|
|
|
|
console.log('✅ 公海需求管理器状态已完全重置 - 采购端');
|
|
}
|
|
}
|
|
|
|
|
|
// 初始化公海需求管理器
|
|
const publicSeaDemandManager = new PublicSeaDemandManager();
|
|
|
|
function goToFollowUpPage() {
|
|
const pageInput = document.getElementById('follow-up-page-input');
|
|
const pageNum = parseInt(pageInput.value, 10);
|
|
|
|
if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= followUpTotalPages) {
|
|
followUpCurrentPage = pageNum;
|
|
updateFollowUpPagination();
|
|
scrollToTableTop();
|
|
} else {
|
|
pageInput.value = followUpCurrentPage;
|
|
alert(`请输入1到${followUpTotalPages}之间的页码`);
|
|
}
|
|
}
|
|
// 为各个客户等级添加分页变量
|
|
let importantCustomersData = [];
|
|
let normalCustomersData = [];
|
|
let lowCustomersData = [];
|
|
let logisticsCustomersData = [];
|
|
let unclassifiedCustomersData = [];
|
|
let companySeaPoolsCustomersData = [];
|
|
let organizationSeaPoolsCustomersData = [];
|
|
let departmentSeaPoolsCustomersData = [];
|
|
|
|
let importantCurrentPage = 1;
|
|
let normalCurrentPage = 1;
|
|
let lowCurrentPage = 1;
|
|
let logisticsCurrentPage = 1;
|
|
let unclassifiedCurrentPage = 1;
|
|
let companySeaPoolsCurrentPage = 1;
|
|
let organizationSeaPoolsCurrentPage = 1;
|
|
let departmentSeaPoolsCurrentPage = 1;
|
|
const CUSTOMERS_PAGE_SIZE = 5;// 每页显示的客户数量
|
|
|
|
// 初始化各个客户等级的分页功能
|
|
function initLevelPagination() {
|
|
// 重要客户分页
|
|
document.getElementById('prev-page-important')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['important'] ?
|
|
window.filteredCustomersData['important'] : importantCustomersData;
|
|
if (importantCurrentPage > 1) {
|
|
importantCurrentPage--;
|
|
renderLevelTable('important', displayData);
|
|
updateLevelPaginationInfo('important', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page-important')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据计算总页数
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['important'] ?
|
|
window.filteredCustomersData['important'] : importantCustomersData;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
if (importantCurrentPage < totalPages) {
|
|
importantCurrentPage++;
|
|
renderLevelTable('important', displayData);
|
|
updateLevelPaginationInfo('important', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-input-important')?.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToLevelPage('important');
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-go-btn-important')?.addEventListener('click', function () {
|
|
goToLevelPage('important');
|
|
});
|
|
|
|
// 普通客户分页
|
|
document.getElementById('prev-page-normal')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['normal'] ?
|
|
window.filteredCustomersData['normal'] : normalCustomersData;
|
|
if (normalCurrentPage > 1) {
|
|
normalCurrentPage--;
|
|
renderLevelTable('normal', displayData);
|
|
updateLevelPaginationInfo('normal', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page-normal')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据计算总页数
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['normal'] ?
|
|
window.filteredCustomersData['normal'] : normalCustomersData;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
if (normalCurrentPage < totalPages) {
|
|
normalCurrentPage++;
|
|
renderLevelTable('normal', displayData);
|
|
updateLevelPaginationInfo('normal', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-input-normal')?.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToLevelPage('normal');
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-go-btn-normal')?.addEventListener('click', function () {
|
|
goToLevelPage('normal');
|
|
});
|
|
|
|
// 低价值客户分页
|
|
document.getElementById('prev-page-low')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['low'] ?
|
|
window.filteredCustomersData['low'] : lowCustomersData;
|
|
if (lowCurrentPage > 1) {
|
|
lowCurrentPage--;
|
|
renderLevelTable('low', displayData);
|
|
updateLevelPaginationInfo('low', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page-low')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据计算总页数
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['low'] ?
|
|
window.filteredCustomersData['low'] : lowCustomersData;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
if (lowCurrentPage < totalPages) {
|
|
lowCurrentPage++;
|
|
renderLevelTable('low', displayData);
|
|
updateLevelPaginationInfo('low', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-input-low')?.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToLevelPage('low');
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-go-btn-low')?.addEventListener('click', function () {
|
|
goToLevelPage('low');
|
|
});
|
|
|
|
// 物流客户分页
|
|
document.getElementById('prev-page-logistics')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['logistics'] ?
|
|
window.filteredCustomersData['logistics'] : logisticsCustomersData;
|
|
if (logisticsCurrentPage > 1) {
|
|
logisticsCurrentPage--;
|
|
renderLevelTable('logistics', displayData);
|
|
updateLevelPaginationInfo('logistics', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page-logistics')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据计算总页数
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['logistics'] ?
|
|
window.filteredCustomersData['logistics'] : logisticsCustomersData;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
if (logisticsCurrentPage < totalPages) {
|
|
logisticsCurrentPage++;
|
|
renderLevelTable('logistics', displayData);
|
|
updateLevelPaginationInfo('logistics', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-input-logistics')?.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToLevelPage('logistics');
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-go-btn-logistics')?.addEventListener('click', function () {
|
|
goToLevelPage('logistics');
|
|
});
|
|
|
|
// 未分级客户分页
|
|
document.getElementById('prev-page-unclassified')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['unclassified'] ?
|
|
window.filteredCustomersData['unclassified'] : unclassifiedCustomersData;
|
|
if (unclassifiedCurrentPage > 1) {
|
|
unclassifiedCurrentPage--;
|
|
renderLevelTable('unclassified', displayData);
|
|
updateLevelPaginationInfo('unclassified', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page-unclassified')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据计算总页数
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['unclassified'] ?
|
|
window.filteredCustomersData['unclassified'] : unclassifiedCustomersData;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
if (unclassifiedCurrentPage < totalPages) {
|
|
unclassifiedCurrentPage++;
|
|
renderLevelTable('unclassified', displayData);
|
|
updateLevelPaginationInfo('unclassified', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-input-unclassified')?.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToLevelPage('unclassified');
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-go-btn-unclassified')?.addEventListener('click', function () {
|
|
goToLevelPage('unclassified');
|
|
});
|
|
|
|
// 公司公海池客户分页
|
|
document.getElementById('prev-page-company-sea-pools')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['company-sea-pools'] ?
|
|
window.filteredCustomersData['company-sea-pools'] : companySeaPoolsCustomersData;
|
|
if (companySeaPoolsCurrentPage > 1) {
|
|
companySeaPoolsCurrentPage--;
|
|
renderLevelTable('company-sea-pools', displayData);
|
|
updateLevelPaginationInfo('company-sea-pools', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page-company-sea-pools')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据计算总页数
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['company-sea-pools'] ?
|
|
window.filteredCustomersData['company-sea-pools'] : companySeaPoolsCustomersData;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
if (companySeaPoolsCurrentPage < totalPages) {
|
|
companySeaPoolsCurrentPage++;
|
|
renderLevelTable('company-sea-pools', displayData);
|
|
updateLevelPaginationInfo('company-sea-pools', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-input-company-sea-pools')?.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToLevelPage('company-sea-pools');
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-go-btn-company-sea-pools')?.addEventListener('click', function () {
|
|
goToLevelPage('company-sea-pools');
|
|
});
|
|
|
|
// 组织公海池客户分页
|
|
document.getElementById('prev-page-organization-sea-pools')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['organization-sea-pools'] ?
|
|
window.filteredCustomersData['organization-sea-pools'] : organizationSeaPoolsCustomersData;
|
|
if (organizationSeaPoolsCurrentPage > 1) {
|
|
organizationSeaPoolsCurrentPage--;
|
|
renderLevelTable('organization-sea-pools', displayData);
|
|
updateLevelPaginationInfo('organization-sea-pools', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page-organization-sea-pools')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据计算总页数
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['organization-sea-pools'] ?
|
|
window.filteredCustomersData['organization-sea-pools'] : organizationSeaPoolsCustomersData;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
if (organizationSeaPoolsCurrentPage < totalPages) {
|
|
organizationSeaPoolsCurrentPage++;
|
|
renderLevelTable('organization-sea-pools', displayData);
|
|
updateLevelPaginationInfo('organization-sea-pools', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-input-organization-sea-pools')?.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToLevelPage('organization-sea-pools');
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-go-btn-organization-sea-pools')?.addEventListener('click', function () {
|
|
goToLevelPage('organization-sea-pools');
|
|
});
|
|
|
|
// 部门公海池客户分页
|
|
document.getElementById('prev-page-department-sea-pools')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['department-sea-pools'] ?
|
|
window.filteredCustomersData['department-sea-pools'] : departmentSeaPoolsCustomersData;
|
|
if (departmentSeaPoolsCurrentPage > 1) {
|
|
departmentSeaPoolsCurrentPage--;
|
|
renderLevelTable('department-sea-pools', displayData);
|
|
updateLevelPaginationInfo('department-sea-pools', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('next-page-department-sea-pools')?.addEventListener('click', function () {
|
|
// 优先使用筛选后的数据计算总页数
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData['department-sea-pools'] ?
|
|
window.filteredCustomersData['department-sea-pools'] : departmentSeaPoolsCustomersData;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
if (departmentSeaPoolsCurrentPage < totalPages) {
|
|
departmentSeaPoolsCurrentPage++;
|
|
renderLevelTable('department-sea-pools', displayData);
|
|
updateLevelPaginationInfo('department-sea-pools', displayData);
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-input-department-sea-pools')?.addEventListener('keypress', function (e) {
|
|
if (e.key === 'Enter') {
|
|
goToLevelPage('department-sea-pools');
|
|
}
|
|
});
|
|
|
|
document.getElementById('page-go-btn-department-sea-pools')?.addEventListener('click', function () {
|
|
goToLevelPage('department-sea-pools');
|
|
});
|
|
}
|
|
// 跳转到指定等级页面的函数
|
|
function goToLevelPage(level) {
|
|
const pageInput = document.getElementById(`page-input-${level}`);
|
|
const pageNum = parseInt(pageInput.value);
|
|
let data, currentPage;
|
|
|
|
// 根据等级获取对应的数据和当前页码
|
|
switch (level) {
|
|
case 'important':
|
|
data = importantCustomersData;
|
|
currentPage = importantCurrentPage;
|
|
break;
|
|
case 'normal':
|
|
data = normalCustomersData;
|
|
currentPage = normalCurrentPage;
|
|
break;
|
|
case 'low':
|
|
data = lowCustomersData;
|
|
currentPage = lowCurrentPage;
|
|
break;
|
|
case 'logistics':
|
|
data = logisticsCustomersData;
|
|
currentPage = logisticsCurrentPage;
|
|
break;
|
|
case 'unclassified':
|
|
data = unclassifiedCustomersData;
|
|
currentPage = unclassifiedCurrentPage;
|
|
break;
|
|
case 'company-sea-pools':
|
|
data = companySeaPoolsCustomersData;
|
|
currentPage = companySeaPoolsCurrentPage;
|
|
break;
|
|
case 'organization-sea-pools':
|
|
data = organizationSeaPoolsCustomersData;
|
|
currentPage = organizationSeaPoolsCurrentPage;
|
|
break;
|
|
case 'department-sea-pools':
|
|
data = departmentSeaPoolsCustomersData;
|
|
currentPage = departmentSeaPoolsCurrentPage;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// 优先使用筛选后的数据(如果存在)
|
|
const displayData = window.filteredCustomersData && window.filteredCustomersData[level] ?
|
|
window.filteredCustomersData[level] : data;
|
|
const totalPages = Math.ceil(displayData.length / CUSTOMERS_PAGE_SIZE);
|
|
|
|
if (!isNaN(pageNum) && pageNum >= 1 && pageNum <= totalPages) {
|
|
// 更新对应等级的当前页码
|
|
switch (level) {
|
|
case 'important':
|
|
importantCurrentPage = pageNum;
|
|
break;
|
|
case 'normal':
|
|
normalCurrentPage = pageNum;
|
|
break;
|
|
case 'low':
|
|
lowCurrentPage = pageNum;
|
|
break;
|
|
case 'logistics':
|
|
logisticsCurrentPage = pageNum;
|
|
break;
|
|
case 'unclassified':
|
|
unclassifiedCurrentPage = pageNum;
|
|
break;
|
|
case 'company-sea-pools':
|
|
companySeaPoolsCurrentPage = pageNum;
|
|
break;
|
|
case 'organization-sea-pools':
|
|
organizationSeaPoolsCurrentPage = pageNum;
|
|
break;
|
|
case 'department-sea-pools':
|
|
departmentSeaPoolsCurrentPage = pageNum;
|
|
break;
|
|
}
|
|
|
|
renderLevelTable(level, displayData);
|
|
updateLevelPaginationInfo(level, displayData);
|
|
} else {
|
|
alert(`请输入1到${totalPages}之间的页码`);
|
|
pageInput.value = currentPage;
|
|
}
|
|
}
|
|
// 高性能表格渲染函数
|
|
function renderLevelTable(level, customers) {
|
|
// 提前缓存常用元素和常量
|
|
const targetTbody = document.getElementById(`${level}-customers`);
|
|
if (!targetTbody) {
|
|
return;
|
|
}
|
|
|
|
// 直接使用传入的customers数据(已在applyTimeFilter中完成筛选)
|
|
let filteredCustomers = customers || [];
|
|
console.log(`${level}等级渲染数据数量: ${filteredCustomers.length}`);
|
|
|
|
// 快速处理空数据情况
|
|
if (!filteredCustomers || filteredCustomers.length === 0) {
|
|
targetTbody.innerHTML = `<tr><td colspan="9" style="text-align: center; color: #666;">暂无${getLevelName(level)}数据</td></tr>`;
|
|
updateLevelPaginationInfo(level, filteredCustomers);
|
|
return;
|
|
}
|
|
|
|
// 使用直接变量引用替代对象映射,减少查找开销
|
|
let currentPage;
|
|
switch (level) {
|
|
case 'important': currentPage = importantCurrentPage; break;
|
|
case 'normal': currentPage = normalCurrentPage; break;
|
|
case 'low': currentPage = lowCurrentPage; break;
|
|
case 'logistics': currentPage = logisticsCurrentPage; break;
|
|
case 'unclassified': currentPage = unclassifiedCurrentPage; break;
|
|
case 'company-sea-pools': currentPage = companySeaPoolsCurrentPage; break;
|
|
case 'organization-sea-pools': currentPage = organizationSeaPoolsCurrentPage; break;
|
|
case 'department-sea-pools': currentPage = departmentSeaPoolsCurrentPage; break;
|
|
default: return;
|
|
}
|
|
|
|
// 计算分页范围
|
|
const pageSize = CUSTOMERS_PAGE_SIZE;
|
|
const startIndex = (currentPage - 1) * pageSize;
|
|
const endIndex = Math.min(startIndex + pageSize, filteredCustomers.length);
|
|
|
|
// 优化:直接在循环中生成HTML字符串,避免创建中间数组和DOM元素
|
|
let htmlString = '';
|
|
const publicSeaLevels = ['company-sea-pools', 'organization-sea-pools', 'department-sea-pools'];
|
|
const isPublicSea = publicSeaLevels.includes(level);
|
|
|
|
// 直接遍历需要的范围
|
|
for (let i = startIndex; i < endIndex; i++) {
|
|
const customer = filteredCustomers[i];
|
|
if (!customer) continue;
|
|
|
|
const company = customer.company || '微信客户';
|
|
const region = customer.region || '-';
|
|
const demand = customer.demand || '-';
|
|
const spec = customer.spec || '-';
|
|
const nickName = customer.nickName || '-';
|
|
const phoneNumber = customer.phoneNumber || '-';
|
|
const createdAt = formatTime(customer.created_at) || '-';
|
|
const updatedAt = formatTime(customer.updated_at) || '-';
|
|
const customerId = customer.id || phoneNumber || '';
|
|
|
|
// 生成操作按钮HTML
|
|
let actionButtonHtml;
|
|
if (isPublicSea) {
|
|
actionButtonHtml = `<button class="action-btn view-details" data-company-id="${customerId}" data-phone="${phoneNumber}"><i class="fas fa-eye"></i> 查看详情</button>`;
|
|
} else {
|
|
actionButtonHtml = `<button class="action-btn follow-up-btn" data-company-id="${customerId}" data-phone="${phoneNumber}"><i class="fas fa-tasks"></i> 跟进</button>`;
|
|
}
|
|
|
|
// 直接拼接HTML字符串
|
|
htmlString += `
|
|
<tr data-id="${customerId}" data-phone="${phoneNumber}" data-level="${level}">
|
|
<td>${company}</td>
|
|
<td>${region}</td>
|
|
<td>${demand}</td>
|
|
<td>${spec}</td>
|
|
<td>${nickName}</td>
|
|
<td>${phoneNumber}</td>
|
|
<td>${createdAt}</td>
|
|
<td>${updatedAt}</td>
|
|
<td class="action-cell">${actionButtonHtml}</td>
|
|
</tr>`;
|
|
}
|
|
|
|
// 一次性设置innerHTML
|
|
targetTbody.innerHTML = htmlString;
|
|
|
|
// 应用懒加载动画效果
|
|
const rows = targetTbody.querySelectorAll('tr');
|
|
if (rows.length > 0) {
|
|
// 使用requestAnimationFrame确保DOM已更新
|
|
requestAnimationFrame(() => {
|
|
rows.forEach((row, index) => {
|
|
// 添加懒加载类
|
|
row.classList.add('lazy-load');
|
|
// 错开动画时间,创造平滑的渐入效果
|
|
setTimeout(() => {
|
|
row.classList.add('loaded');
|
|
}, index * 15); // 每个行延迟15ms,创造流水线效果
|
|
});
|
|
});
|
|
}
|
|
|
|
// 更新分页信息
|
|
updateLevelPaginationInfo(level, filteredCustomers);
|
|
}
|
|
|
|
// 高性能的客户表格行创建函数
|
|
function createCustomerTableRow(customer, level) {
|
|
if (!customer) return null;
|
|
|
|
const row = document.createElement('tr');
|
|
|
|
// 设置数据属性
|
|
row.dataset.id = customer.id || customer.phoneNumber || '';
|
|
row.dataset.phone = customer.phoneNumber || '';
|
|
row.dataset.level = level;
|
|
|
|
// 生成所有表格单元格的HTML
|
|
const company = customer.company || '微信客户';
|
|
const region = customer.region || '-';
|
|
const demand = customer.demand || '-';
|
|
const spec = customer.spec || '-';
|
|
const nickName = customer.nickName || '-';
|
|
const phoneNumber = customer.phoneNumber || '-';
|
|
const createdAt = formatTime(customer.created_at) || '-';
|
|
const updatedAt = formatTime(customer.updated_at) || '-';
|
|
|
|
// 生成操作按钮HTML
|
|
const publicSeaLevels = ['company-sea-pools', 'organization-sea-pools', 'department-sea-pools'];
|
|
const isPublicSea = publicSeaLevels.includes(level);
|
|
let actionButtonHtml;
|
|
|
|
if (isPublicSea) {
|
|
actionButtonHtml = `
|
|
<button class="action-btn view-details"
|
|
data-company-id="${customer.id || ''}"
|
|
data-phone="${phoneNumber}">
|
|
<i class="fas fa-eye"></i> 查看详情
|
|
</button>`;
|
|
} else {
|
|
actionButtonHtml = `
|
|
<button class="action-btn follow-up-btn"
|
|
data-company-id="${customer.id || ''}"
|
|
data-phone="${phoneNumber}">
|
|
<i class="fas fa-tasks"></i> 跟进
|
|
</button>`;
|
|
}
|
|
|
|
// 一次性设置所有HTML
|
|
row.innerHTML = `
|
|
<td>${company}</td>
|
|
<td>${region}</td>
|
|
<td>${demand}</td>
|
|
<td>${spec}</td>
|
|
<td>${nickName}</td>
|
|
<td>${phoneNumber}</td>
|
|
<td>${createdAt}</td>
|
|
<td>${updatedAt}</td>
|
|
<td class="action-cell">${actionButtonHtml}</td>
|
|
`;
|
|
|
|
return row;
|
|
}
|
|
|
|
// 优化的事件委托函数 - 统一处理所有客户表格交互
|
|
function setupEnhancedEventDelegation() {
|
|
// 获取包含所有表格的容器元素,使用统一委托方式
|
|
const tablesContainer = document.querySelector('.tab-content'); // 假设所有表格都在.tab-content中
|
|
|
|
if (!tablesContainer) {
|
|
// 如果找不到统一容器,则使用原来的选择器方式
|
|
const allTables = document.querySelectorAll('#important-table, #normal-table, #low-table, #logistics-table, #unclassified-table, #all-table, #company-sea-pools-table, #organization-sea-pools-table, #department-sea-pools-table');
|
|
|
|
allTables.forEach(table => {
|
|
table.addEventListener('click', handleTableClick);
|
|
});
|
|
} else {
|
|
// 使用单个事件监听器处理所有表格交互
|
|
tablesContainer.addEventListener('click', handleTableClick);
|
|
}
|
|
}
|
|
|
|
// 统一的表格点击处理函数
|
|
function handleTableClick(e) {
|
|
// 检查是否点击了表格中的操作按钮
|
|
const actionButton = e.target.closest('.action-btn');
|
|
if (actionButton) {
|
|
e.stopPropagation(); // 阻止事件冒泡
|
|
const customerId = actionButton.dataset.companyId;
|
|
const phoneNumber = actionButton.dataset.phone;
|
|
|
|
if (actionButton.classList.contains('view-details')) {
|
|
// 查看详情按钮
|
|
viewCustomerDetails(customerId, phoneNumber);
|
|
} else if (actionButton.classList.contains('follow-up-btn')) {
|
|
// 修复:跟进按钮应该调用showFollowUpInterface
|
|
showFollowUpInterface(customerId, phoneNumber);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 处理表格行点击
|
|
const row = e.target.closest('tr');
|
|
if (row && !e.target.closest('button')) {
|
|
const customerId = row.dataset.id;
|
|
const phoneNumber = row.dataset.phone;
|
|
|
|
if (customerId || phoneNumber) {
|
|
viewCustomerDetails(customerId, phoneNumber);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 在DOMContentLoaded时只绑定一次事件委托
|
|
window.addEventListener('DOMContentLoaded', setupEnhancedEventDelegation);
|
|
// 更新各个等级的分页信息
|
|
function updateLevelPaginationInfo(level, filteredData = null) {
|
|
let data, currentPage, totalPages, totalInfoElement, pageInfoElement, prevButton, nextButton;
|
|
// 根据等级获取对应的数据和元素
|
|
switch (level) {
|
|
case 'important':
|
|
data = filteredData || importantCustomersData;
|
|
currentPage = importantCurrentPage;
|
|
totalPages = Math.ceil(data.length / CUSTOMERS_PAGE_SIZE);
|
|
totalInfoElement = document.getElementById('important-total-info');
|
|
pageInfoElement = document.getElementById('important-page-info');
|
|
prevButton = document.getElementById('prev-page-important');
|
|
nextButton = document.getElementById('next-page-important');
|
|
break;
|
|
case 'normal':
|
|
data = filteredData || normalCustomersData;
|
|
currentPage = normalCurrentPage;
|
|
totalPages = Math.ceil(data.length / CUSTOMERS_PAGE_SIZE);
|
|
totalInfoElement = document.getElementById('normal-total-info');
|
|
pageInfoElement = document.getElementById('normal-page-info');
|
|
prevButton = document.getElementById('prev-page-normal');
|
|
nextButton = document.getElementById('next-page-normal');
|
|
break;
|
|
case 'low':
|
|
data = filteredData || lowCustomersData;
|
|
currentPage = lowCurrentPage;
|
|
totalPages = Math.ceil(data.length / CUSTOMERS_PAGE_SIZE);
|
|
totalInfoElement = document.getElementById('low-total-info');
|
|
pageInfoElement = document.getElementById('low-page-info');
|
|
prevButton = document.getElementById('prev-page-low');
|
|
nextButton = document.getElementById('next-page-low');
|
|
break;
|
|
case 'logistics':
|
|
data = filteredData || logisticsCustomersData;
|
|
currentPage = logisticsCurrentPage;
|
|
totalPages = Math.ceil(data.length / CUSTOMERS_PAGE_SIZE);
|
|
totalInfoElement = document.getElementById('logistics-total-info');
|
|
pageInfoElement = document.getElementById('logistics-page-info');
|
|
prevButton = document.getElementById('prev-page-logistics');
|
|
nextButton = document.getElementById('next-page-logistics');
|
|
break;
|
|
case 'unclassified':
|
|
data = filteredData || unclassifiedCustomersData;
|
|
currentPage = unclassifiedCurrentPage;
|
|
totalPages = Math.ceil(data.length / CUSTOMERS_PAGE_SIZE);
|
|
totalInfoElement = document.getElementById('unclassified-total-info');
|
|
pageInfoElement = document.getElementById('unclassified-page-info');
|
|
prevButton = document.getElementById('prev-page-unclassified');
|
|
nextButton = document.getElementById('next-page-unclassified');
|
|
break;
|
|
case 'company-sea-pools':
|
|
data = filteredData || companySeaPoolsCustomersData;
|
|
currentPage = companySeaPoolsCurrentPage;
|
|
totalPages = Math.ceil(data.length / CUSTOMERS_PAGE_SIZE);
|
|
totalInfoElement = document.getElementById('company-sea-pools-total-info');
|
|
pageInfoElement = document.getElementById('company-sea-pools-page-info');
|
|
prevButton = document.getElementById('prev-page-company-sea-pools');
|
|
nextButton = document.getElementById('next-page-company-sea-pools');
|
|
break;
|
|
case 'organization-sea-pools':
|
|
data = filteredData || organizationSeaPoolsCustomersData;
|
|
currentPage = organizationSeaPoolsCurrentPage;
|
|
totalPages = Math.ceil(data.length / CUSTOMERS_PAGE_SIZE);
|
|
totalInfoElement = document.getElementById('organization-sea-pools-total-info');
|
|
pageInfoElement = document.getElementById('organization-sea-pools-page-info');
|
|
prevButton = document.getElementById('prev-page-organization-sea-pools');
|
|
nextButton = document.getElementById('next-page-organization-sea-pools');
|
|
break;
|
|
case 'department-sea-pools':
|
|
data = filteredData || departmentSeaPoolsCustomersData;
|
|
currentPage = departmentSeaPoolsCurrentPage;
|
|
totalPages = Math.ceil(data.length / CUSTOMERS_PAGE_SIZE);
|
|
totalInfoElement = document.getElementById('department-sea-pools-total-info');
|
|
pageInfoElement = document.getElementById('department-sea-pools-page-info');
|
|
prevButton = document.getElementById('prev-page-department-sea-pools');
|
|
nextButton = document.getElementById('next-page-department-sea-pools');
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (pageInfoElement) {
|
|
pageInfoElement.textContent = `第 ${currentPage} 页,共 ${totalPages} 页`;
|
|
}
|
|
|
|
if (totalInfoElement) {
|
|
const startIndex = (currentPage - 1) * CUSTOMERS_PAGE_SIZE + 1;
|
|
const endIndex = Math.min(startIndex + CUSTOMERS_PAGE_SIZE - 1, data.length);
|
|
totalInfoElement.textContent = `显示 ${startIndex}-${endIndex} 条 / 总计: ${data.length} 条`;
|
|
}
|
|
|
|
// 更新分页按钮状态
|
|
if (prevButton) {
|
|
prevButton.disabled = currentPage === 1;
|
|
}
|
|
|
|
if (nextButton) {
|
|
nextButton.disabled = currentPage === totalPages || totalPages === 0;
|
|
}
|
|
}
|
|
|
|
|
|
function updateFollowUpTotalInfo(totalCount, currentPageCount) {
|
|
const totalInfoElement = document.getElementById('follow-up-total-info');
|
|
if (totalInfoElement) {
|
|
totalInfoElement.textContent = `当前页面: ${currentPageCount}条 / 总计: ${totalCount}条`;
|
|
}
|
|
}
|
|
|
|
function updateFollowUpPagination() {
|
|
const allRecords = Array.from(document.querySelectorAll('#follow-up-table tbody tr'));
|
|
|
|
const filteredRecords = followUpCurrentSearchTerm
|
|
? allRecords.filter(record => {
|
|
const text = record.textContent.toLowerCase();
|
|
return text.includes(followUpCurrentSearchTerm);
|
|
})
|
|
: allRecords;
|
|
|
|
const totalItems = filteredRecords.length;
|
|
|
|
followUpTotalPages = followUpShowAll ? 1 : Math.max(1, Math.ceil(totalItems / followUpPAGE_SIZE));
|
|
|
|
followUpCurrentPage = Math.min(Math.max(1, followUpCurrentPage), followUpTotalPages);
|
|
|
|
document.getElementById('follow-up-page-info').textContent = `第 ${followUpCurrentPage} 页,共 ${followUpTotalPages} 页`;
|
|
|
|
document.getElementById('follow-up-page-input').value = followUpCurrentPage;
|
|
document.getElementById('follow-up-page-input').max = followUpTotalPages;
|
|
|
|
document.getElementById('follow-up-prev-page').disabled = followUpCurrentPage === 1 || followUpShowAll;
|
|
document.getElementById('follow-up-next-page').disabled = followUpCurrentPage === followUpTotalPages || followUpShowAll;
|
|
|
|
const currentPageRecords = displayFollowUpCurrentPageRecords(filteredRecords);
|
|
|
|
updateFollowUpTotalInfo(totalItems, currentPageRecords.length);
|
|
}
|
|
|
|
function displayFollowUpCurrentPageRecords(filteredRecords) {
|
|
const allRecords = document.querySelectorAll('#follow-up-table tbody tr');
|
|
allRecords.forEach(record => {
|
|
record.style.display = 'none';
|
|
});
|
|
|
|
const startIndex = followUpShowAll ? 0 : (followUpCurrentPage - 1) * followUpPAGE_SIZE;
|
|
const endIndex = followUpShowAll ? filteredRecords.length : startIndex + followUpPAGE_SIZE;
|
|
const currentPageRecords = filteredRecords.slice(startIndex, endIndex);
|
|
|
|
currentPageRecords.forEach(record => {
|
|
record.style.display = '';
|
|
});
|
|
|
|
return currentPageRecords;
|
|
}
|
|
|
|
const API_BASE_URL = '/DL/supply'; // 后端API基础地址 - 使用相对路径
|
|
// 有效等级配置
|
|
const LEVEL_CONFIG = {
|
|
validLevels: ['important', 'normal', 'low', 'logistics', 'unclassified',
|
|
'company-sea-pools', 'organization-sea-pools', 'department-sea-pools'],
|
|
displayNames: {
|
|
'important': '重要客户',
|
|
'normal': '普通客户',
|
|
'low': '低价值客户',
|
|
'logistics': '物流客户',
|
|
'unclassified': '未分级客户',
|
|
'company-sea-pools': '公司公海池',
|
|
'organization-sea-pools': '组织公海池',
|
|
'department-sea-pools': '部门公海池'
|
|
}
|
|
};
|
|
// 前端等级到标准化等级的映射
|
|
const frontendToStandardized = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'low': 'low-value',
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified',
|
|
'company-sea-pools': 'company-sea-pools',
|
|
'organization-sea-pools': 'organization-sea-pools',
|
|
'department-sea-pools': 'department-sea-pools',
|
|
'all': 'all'
|
|
};
|
|
// 添加CSS样式,为所有客户表格行添加指针样式和悬停效果
|
|
const additionalCSS = `
|
|
/* 为所有客户表格行添加指针样式 */
|
|
#customers-table tbody tr,
|
|
#recent-customers-table tbody tr,
|
|
#important-customers tr,
|
|
#normal-customers tr,
|
|
#low-customers tr,
|
|
#logistics-customers tr,
|
|
#unclassified-customers tr,
|
|
#company-sea-pools-customers tr,
|
|
#organization-sea-pools-customers tr,
|
|
#department-sea-pools-customers tr,
|
|
#all-customers tr {
|
|
cursor: pointer;
|
|
transition: background-color 0.15s ease;
|
|
background-color: transparent !important;
|
|
}
|
|
|
|
/* 为所有客户表格行添加悬停效果 */
|
|
#customers-table tbody tr:hover,
|
|
#recent-customers-table tbody tr:hover,
|
|
#important-customers tr:hover,
|
|
#normal-customers tr:hover,
|
|
#low-customers tr:hover,
|
|
#logistics-customers tr:hover,
|
|
#unclassified-customers tr:hover,
|
|
#company-sea-pools-customers tr:hover,
|
|
#organization-sea-pools-customers tr:hover,
|
|
#department-sea-pools-customers tr:hover,
|
|
#all-customers tr:hover {
|
|
background-color: rgba(232, 106, 146, 0.08) !important;
|
|
}
|
|
|
|
/* 点击时的临时效果 */
|
|
#customers-table tbody tr:active,
|
|
#recent-customers-table tbody tr:active,
|
|
#important-customers tr:active,
|
|
#normal-customers tr:active,
|
|
#low-customers tr:active,
|
|
#logistics-customers tr:active,
|
|
#unclassified-customers tr:active,
|
|
#company-sea-pools-customers tr:active,
|
|
#organization-sea-pools-customers tr:active,
|
|
#department-sea-pools-customers tr:active,
|
|
#all-customers tr:active {
|
|
background-color: rgba(232, 106, 146, 0.15) !important;
|
|
}
|
|
|
|
/* 操作按钮区域样式 */
|
|
.action-btn {
|
|
pointer-events: auto !important;
|
|
z-index: 10;
|
|
position: relative;
|
|
}
|
|
|
|
/* 确保操作按钮可点击且不影响行点击 */
|
|
.action-cell {
|
|
pointer-events: auto;
|
|
}
|
|
`;
|
|
|
|
// 将CSS样式添加到页面
|
|
const styleElement = document.createElement('style');
|
|
styleElement.textContent = additionalCSS;
|
|
document.head.appendChild(styleElement);
|
|
|
|
console.log('已为所有客户等级添加点击查看详情功能');
|
|
|
|
|
|
// 修改表单提交事件处理函数
|
|
document.getElementById('addCustomerForm').addEventListener('submit', async (e) => {
|
|
e.preventDefault();
|
|
|
|
// 获取登录信息
|
|
const loginInfo = getLoginInfo();
|
|
|
|
// 验证必填项
|
|
const addCompany = document.getElementById('add-company');
|
|
const addContact = document.getElementById('add-contact');
|
|
const addPhone = document.getElementById('add-phone');
|
|
const addLevel = document.getElementById('customer-levels');
|
|
const validLevels = ['important', 'normal', 'low-value', 'logistics', 'unclassified'];
|
|
|
|
if (!addCompany.value.trim() || !addContact.value.trim() || !addPhone.value.trim() ||
|
|
!addLevel.value || !validLevels.includes(addLevel.value)) {
|
|
alert('客户公司、联系人、电话和有效的客户等级为必填项!');
|
|
return;
|
|
}
|
|
|
|
let originalText = '';
|
|
try {
|
|
// 显示加载状态
|
|
const confirmButton = document.getElementById('confirmAddCustomer');
|
|
originalText = confirmButton.innerHTML;
|
|
confirmButton.disabled = true;
|
|
confirmButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 提交中...';
|
|
|
|
// 获取用户修改后的协助人信息
|
|
const modifiedAssistant = document.getElementById('add-assistant').value.trim();
|
|
|
|
// 构建请求参数,包含登录信息
|
|
const formData = new URLSearchParams();
|
|
const now = new Date();
|
|
const isoDateTime = formatToLocalDateTime(now, true);
|
|
|
|
// 基本客户信息
|
|
formData.append('id', document.getElementById('add-id').value);
|
|
formData.append('company', addCompany.value.trim());
|
|
formData.append('region', document.getElementById('add-region').value.trim());
|
|
formData.append('level', addLevel.value);
|
|
formData.append('type', document.getElementById('add-type').value.trim());
|
|
formData.append('demand', document.getElementById('add-needs').value.trim());
|
|
formData.append('spec', document.getElementById('add-spec').value.trim());
|
|
formData.append('nickName', document.getElementById('add-contact').value.trim());
|
|
formData.append('phoneNumber', document.getElementById('add-phone').value.trim());
|
|
formData.append('wechat', document.getElementById('add-wechat').value.trim());
|
|
formData.append('account', document.getElementById('add-account').value.trim());
|
|
formData.append('accountNumber', document.getElementById('add-account-number').value.trim());
|
|
formData.append('bank', document.getElementById('add-bank').value.trim());
|
|
formData.append('address', document.getElementById('add-address').value.trim());
|
|
|
|
// 负责人信息(使用登录信息,但协助人使用用户修改后的值)
|
|
formData.append('managerId', loginInfo.managerId);
|
|
formData.append('managercompany', loginInfo.managercompany);
|
|
formData.append('managerdepartment', loginInfo.managerdepartment);
|
|
formData.append('organization', loginInfo.organization);
|
|
formData.append('role', loginInfo.role);
|
|
formData.append('userName', loginInfo.userName);
|
|
formData.append('assistant', modifiedAssistant || loginInfo.assistant); // 使用修改后的协助人或原始值
|
|
|
|
// 时间信息
|
|
formData.append('created_at', isoDateTime);
|
|
formData.append('updated_at', isoDateTime);
|
|
|
|
if (formData.created_at) {
|
|
formData.created_at = formData.created_at.replace('T', '');
|
|
}
|
|
|
|
// 发送POST请求到后端
|
|
const response = await fetch(`${API_BASE_URL}/pool/inputcustomers`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: formData.toString()
|
|
});
|
|
|
|
const responseData = await response.json();
|
|
|
|
if (!response.ok || !responseData.success) {
|
|
throw new Error(responseData.message || `提交失败 (${response.status})`);
|
|
}
|
|
|
|
// 显示成功消息
|
|
const levelTextMap = {
|
|
'important': '重要客户',
|
|
'normal': '普通客户',
|
|
'low-value': '低价值客户',
|
|
'logistics': '物流客户',
|
|
'unclassified': '未分级客户'
|
|
};
|
|
alert(`客户已成功添加到${levelTextMap[addLevel.value]}!`);
|
|
|
|
// 关闭弹窗并重置表单
|
|
closeAddCustomerModal();
|
|
|
|
// 刷新数据
|
|
refreshCustomerData(addLevel.value);
|
|
|
|
} catch (error) {
|
|
console.error('新增客户过程出错:', error);
|
|
alert(`操作提示: ${error.message}`);
|
|
} finally {
|
|
// 恢复按钮状态
|
|
const confirmButton = document.getElementById('confirmAddCustomer');
|
|
confirmButton.disabled = false;
|
|
confirmButton.innerHTML = originalText;
|
|
}
|
|
});
|
|
|
|
// 修改现有的行点击事件绑定,确保所有表格都支持
|
|
// 增强事件委托机制,确保所有客户行都能触发详情查看
|
|
function setupEnhancedEventDelegation() {
|
|
// 统一的客户表格点击处理
|
|
document.addEventListener('click', function(e) {
|
|
// 处理跟进按钮点击
|
|
if (e.target.closest('.follow-up-btn')) {
|
|
e.stopPropagation();
|
|
const btn = e.target.closest('.follow-up-btn');
|
|
const customerId = btn.dataset.companyId;
|
|
const phoneNumber = btn.dataset.phone;
|
|
|
|
if (customerId || phoneNumber) {
|
|
// 修复:跟进按钮应该打开跟进界面,而不是客户详情
|
|
showFollowUpInterface(customerId, phoneNumber);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 处理查看详情按钮点击
|
|
if (e.target.closest('.view-details')) {
|
|
e.stopPropagation();
|
|
const btn = e.target.closest('.view-details');
|
|
const customerId = btn.dataset.companyId;
|
|
const phoneNumber = btn.dataset.phone;
|
|
|
|
if (customerId || phoneNumber) {
|
|
viewCustomerDetails(customerId, phoneNumber);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 处理客户行点击(排除按钮区域)
|
|
const row = e.target.closest('tr');
|
|
if (row && !e.target.closest('button') && !e.target.closest('.action-cell')) {
|
|
const customerId = row.dataset.id || row.dataset.companyId;
|
|
const phoneNumber = row.dataset.phone ||
|
|
row.querySelector('td:nth-child(6)')?.textContent?.trim() ||
|
|
row.querySelector('td:nth-child(3)')?.textContent?.trim();
|
|
|
|
console.log('行点击详情查看:', { customerId, phoneNumber });
|
|
|
|
if (customerId || phoneNumber) {
|
|
viewCustomerDetails(customerId, phoneNumber);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 统一的表格行点击事件绑定函数
|
|
// 在表格行点击事件中传递targetProductItemId
|
|
function bindTableRowClickEvents() {
|
|
// 绑定客户管理页面表格行点击事件
|
|
const customerTables = [
|
|
'#important-customers tr',
|
|
'#normal-customers tr',
|
|
'#low-customers tr',
|
|
'#logistics-customers tr',
|
|
'#unclassified-customers tr',
|
|
'#company-sea-pools-customers tr',
|
|
'#organization-sea-pools-customers tr',
|
|
'#department-sea-pools-customers tr',
|
|
'#all-customers tr'
|
|
];
|
|
|
|
customerTables.forEach(selector => {
|
|
document.querySelectorAll(selector).forEach(row => {
|
|
// 移除现有的事件监听器,避免重复绑定
|
|
const newRow = row.cloneNode(true);
|
|
row.parentNode.replaceChild(newRow, row);
|
|
|
|
newRow.addEventListener('click', function (e) {
|
|
// 排除操作按钮的点击
|
|
if (e.target.tagName === 'BUTTON' || e.target.closest('button')) {
|
|
return;
|
|
}
|
|
|
|
const customerId = this.getAttribute('data-id');
|
|
const phoneNumber = this.getAttribute('data-phone') ||
|
|
this.querySelector('td:nth-child(6)')?.textContent?.trim() || '';
|
|
|
|
// 🔥 如果有存储的选中需求ID,传递它
|
|
const targetProductItemId = this.getAttribute('data-target-cart-item-id');
|
|
|
|
if (customerId || phoneNumber) {
|
|
viewCustomerDetails(customerId, phoneNumber, targetProductItemId);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// 绑定最近客户表格行点击事件
|
|
document.querySelectorAll('#recent-customers-table tbody tr').forEach(row => {
|
|
const newRow = row.cloneNode(true);
|
|
row.parentNode.replaceChild(newRow, row);
|
|
|
|
newRow.addEventListener('click', function (e) {
|
|
if (e.target.tagName === 'BUTTON' || e.target.closest('button')) {
|
|
return;
|
|
}
|
|
|
|
const customerId = this.getAttribute('data-id');
|
|
const phoneNumber = this.getAttribute('data-phone') ||
|
|
this.querySelector('td:nth-child(3)')?.textContent?.trim() || '';
|
|
|
|
// 🔥 如果有存储的选中需求ID,传递它
|
|
const targetProductItemId = this.getAttribute('data-target-cart-item-id');
|
|
|
|
if (customerId || phoneNumber) {
|
|
viewCustomerDetails(customerId, phoneNumber, targetProductItemId);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
// 页面加载时生成客户ID
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
// 初始化用户信息下拉菜单
|
|
initUserInfoDropdown();
|
|
|
|
initAllCustomersPagination();
|
|
initLevelPagination();
|
|
|
|
// 确保初始时有ID值
|
|
const idInput = document.getElementById('add-id');
|
|
if (idInput && !idInput.value) {
|
|
idInput.value = generateCustomerId();
|
|
}
|
|
|
|
// 初始化本地客户数据存储
|
|
window.customerData = window.customerData || {};
|
|
|
|
// 绑定新增客户按钮事件
|
|
document.getElementById('dashboard-add-customer')?.addEventListener('click', openAddCustomerModal);
|
|
document.getElementById('customers-add-customer')?.addEventListener('click', openAddCustomerModal);
|
|
|
|
// 关闭新增客户弹窗
|
|
document.getElementById('closeAddCustomerModal')?.addEventListener('click', closeAddCustomerModal);
|
|
document.getElementById('cancelAddCustomer')?.addEventListener('click', closeAddCustomerModal);
|
|
|
|
// 优化:只加载当前选中的等级数据,而不是所有数据
|
|
const currentActiveLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'all';
|
|
refreshCustomerData(currentActiveLevel);
|
|
|
|
// 初始化电话号码校验功能
|
|
initPhoneCheck();
|
|
|
|
// 初始化模态框事件
|
|
initModalEvents();
|
|
// 设置等级标签切换
|
|
setupLevelTabs();
|
|
|
|
// 初始加载所有客户数据
|
|
refreshCustomerData('all');
|
|
});
|
|
|
|
// 打开新增客户弹窗
|
|
function openAddCustomerModal() {
|
|
document.getElementById('addCustomerModal').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// 关键修复:确保每次打开弹窗时都生成新的ID
|
|
const idInput = document.getElementById('add-id');
|
|
if (idInput) {
|
|
idInput.value = generateCustomerId();
|
|
console.log('生成的客户ID:', idInput.value);
|
|
}
|
|
|
|
// 打印登录信息
|
|
console.log('=== 新增客户弹窗打开,开始填充负责人信息 ===');
|
|
const loginInfo = getLoginInfo();
|
|
|
|
// 自动填充负责人信息(协助人字段可修改)
|
|
fillManagerInfoWithLogin(loginInfo, 'add');
|
|
|
|
// 重置表单,但保留ID值和负责人信息
|
|
const form = document.getElementById('addCustomerForm');
|
|
if (form) {
|
|
// 先保存当前ID值和负责人信息
|
|
const currentId = idInput.value;
|
|
form.reset();
|
|
// 恢复ID值
|
|
idInput.value = currentId;
|
|
// 重新填充负责人信息(协助人字段允许用户修改)
|
|
fillManagerInfoWithLogin(loginInfo, 'add');
|
|
}
|
|
|
|
// 重置需求选择器
|
|
if (window.needsSelect) {
|
|
window.needsSelect.clearSelections();
|
|
}
|
|
|
|
// 重置客户等级和类型选择器
|
|
resetLevelSelectors();
|
|
|
|
console.log('=== 新增客户弹窗负责人信息填充完成 ===');
|
|
}
|
|
|
|
// 填充负责人信息的通用函数
|
|
function fillManagerInfoWithLogin(loginInfo, mode) {
|
|
console.log(`填充负责人信息 - 模式: ${mode}`, loginInfo);
|
|
|
|
const fields = {
|
|
'managerId': loginInfo.managerId,
|
|
'managercompany': loginInfo.managercompany,
|
|
'managerdepartment': loginInfo.managerdepartment,
|
|
'organization': loginInfo.organization,
|
|
'role': loginInfo.role,
|
|
'userName': loginInfo.userName,
|
|
'assistant': loginInfo.assistant
|
|
};
|
|
|
|
Object.entries(fields).forEach(([field, value]) => {
|
|
const element = document.getElementById(`${mode}-${field}`);
|
|
if (element) {
|
|
element.value = value;
|
|
console.log(`设置 ${field}: ${value}`);
|
|
|
|
// 在新增模式下,只有协助人字段允许修改
|
|
if (mode === 'add') {
|
|
if (field === 'assistant') {
|
|
// 协助人字段可编辑
|
|
element.readOnly = false;
|
|
element.style.backgroundColor = '#fff';
|
|
element.style.cursor = 'text';
|
|
element.style.border = '1px solid #ddd';
|
|
element.title = '可修改协助人信息';
|
|
} else {
|
|
// 其他负责人信息字段保持只读
|
|
element.readOnly = true;
|
|
element.style.backgroundColor = '#f5f5f5';
|
|
element.style.cursor = 'not-allowed';
|
|
}
|
|
}
|
|
} else {
|
|
console.warn(`未找到元素: ${mode}-${field}`);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 新增:重置客户等级和类型选择器
|
|
function resetLevelSelectors() {
|
|
// 重置客户等级选择器
|
|
const levelTrigger = document.querySelector('#addCustomerModal .level-select-trigger');
|
|
if (levelTrigger) {
|
|
const placeholder = levelTrigger.querySelector('.placeholder');
|
|
const selectedText = levelTrigger.querySelector('.selected-text');
|
|
const hiddenInput = levelTrigger.querySelector('input[type="hidden"]');
|
|
|
|
if (placeholder) placeholder.style.display = 'block';
|
|
if (selectedText) selectedText.remove();
|
|
if (hiddenInput) hiddenInput.value = '';
|
|
|
|
// 移除激活样式
|
|
levelTrigger.classList.remove('active');
|
|
}
|
|
|
|
// 重置客户类型选择器
|
|
const typeTrigger = document.querySelector('#addCustomerModal .level-select-container:last-child .level-select-trigger');
|
|
if (typeTrigger) {
|
|
const placeholder = typeTrigger.querySelector('.placeholder');
|
|
const selectedText = typeTrigger.querySelector('.selected-text');
|
|
const hiddenInput = typeTrigger.querySelector('input[type="hidden"]');
|
|
|
|
if (placeholder) placeholder.style.display = 'block';
|
|
if (selectedText) selectedText.remove();
|
|
if (hiddenInput) hiddenInput.value = '';
|
|
|
|
// 移除激活样式
|
|
typeTrigger.classList.remove('active');
|
|
}
|
|
}
|
|
|
|
|
|
// 关闭新增客户弹窗
|
|
function closeAddCustomerModal() {
|
|
document.getElementById('addCustomerModal').classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
|
|
// 关键修复:预生成下一个ID,确保下次打开时有值
|
|
setTimeout(() => {
|
|
const idInput = document.getElementById('add-id');
|
|
if (idInput) {
|
|
idInput.value = generateCustomerId();
|
|
}
|
|
}, 100);
|
|
}
|
|
// 修改:获取公海池数据的函数
|
|
async function fetchPublicSeaCustomers() {
|
|
try {
|
|
const response = await fetch(appendAuthParams(`${API_BASE_URL}/pool/all-customers`), {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP错误: ${response.status}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.message || '获取公海池数据失败');
|
|
}
|
|
|
|
// 关键修改:将 Map 对象转换为数组
|
|
const data = result.data || {};
|
|
let customersArray = [];
|
|
|
|
if (Array.isArray(data)) {
|
|
customersArray = data;
|
|
} else if (typeof data === 'object' && data !== null) {
|
|
// 如果是对象/Map,转换为数组
|
|
customersArray = Object.values(data);
|
|
}
|
|
|
|
console.log('数据转换结果:', {
|
|
原始数据类型: typeof data,
|
|
是数组: Array.isArray(data),
|
|
转换后数组长度: customersArray.length
|
|
});
|
|
|
|
return customersArray;
|
|
} catch (error) {
|
|
console.error('获取公海池数据失败:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 修改:根据登录信息过滤公海池数据(添加类型检查)
|
|
function filterPublicSeaCustomersByLoginInfo(customers, loginInfo, level) {
|
|
console.log('根据登录信息过滤公海池数据:', {
|
|
loginInfo,
|
|
level,
|
|
输入数据类型: typeof customers,
|
|
是数组: Array.isArray(customers)
|
|
});
|
|
|
|
// 双重保障:确保 customers 是数组
|
|
if (!customers || !Array.isArray(customers)) {
|
|
console.error('filterPublicSeaCustomersByLoginInfo: customers 参数不是数组', customers);
|
|
// 如果不是数组,尝试转换为数组
|
|
if (customers && typeof customers === 'object') {
|
|
customers = Object.values(customers);
|
|
console.log('在过滤函数中已转换为数组:', customers);
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
return customers.filter(customer => {
|
|
|
|
// 根据不同的公海池级别进行过滤
|
|
switch (level) {
|
|
case 'company-sea-pools':
|
|
// 公司公海池:所有人都能看到
|
|
return true;
|
|
|
|
case 'organization-sea-pools':
|
|
// 组织公海池:只显示登录用户所在组织的公海池
|
|
return customer.organization === loginInfo.organization;
|
|
|
|
case 'department-sea-pools':
|
|
// 部门公海池:只显示登录用户所在部门的公海池
|
|
return customer.managerdepartment === loginInfo.managerdepartment;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 添加数据缓存机制
|
|
const dataCache = {
|
|
cache: {},
|
|
set: function(key, data, ttl = 300000) { // 默认5分钟缓存
|
|
this.cache[key] = {
|
|
data: data,
|
|
expiry: Date.now() + ttl
|
|
};
|
|
},
|
|
get: function(key) {
|
|
const item = this.cache[key];
|
|
if (item && Date.now() < item.expiry) {
|
|
return item.data;
|
|
}
|
|
// 缓存过期或不存在
|
|
delete this.cache[key];
|
|
return null;
|
|
},
|
|
clear: function(key) {
|
|
if (key) {
|
|
delete this.cache[key];
|
|
} else {
|
|
this.cache = {};
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// 优化的客户数据刷新函数,使用requestAnimationFrame和完善的错误处理
|
|
let activeDataRefreshRequests = new Map(); // 跟踪活跃的刷新请求,避免重复请求
|
|
|
|
function refreshCustomerData(level) {
|
|
console.log(`🔄 请求客户数据,等级: ${level}`);
|
|
|
|
// 检查是否有相同等级的刷新请求正在进行,如果有则返回该Promise
|
|
if (activeDataRefreshRequests.has(level)) {
|
|
console.log(`⏳ 已有相同等级(${level})的数据刷新请求正在进行,等待结果`);
|
|
return activeDataRefreshRequests.get(level);
|
|
}
|
|
|
|
// 创建新的Promise用于此刷新请求
|
|
const refreshPromise = new Promise((resolve, reject) => {
|
|
// 首先尝试从缓存获取
|
|
const cachedData = window.customerCache.get(level);
|
|
|
|
if (cachedData) {
|
|
console.log(`✅ 使用缓存数据渲染: ${level}`);
|
|
|
|
// 使用requestAnimationFrame确保渲染在下一帧进行,避免阻塞UI
|
|
requestAnimationFrame(() => {
|
|
try {
|
|
const processedData = optimizedProcessFilteredCustomers(cachedData, level);
|
|
// 缓存加载完毕后,触发最近客户的刷新,使所有客户数据在同一时间点渲染
|
|
if (level === 'all' && typeof refreshRecentCustomers === 'function') {
|
|
refreshRecentCustomers(processedData || cachedData);
|
|
}
|
|
resolve(processedData || cachedData);
|
|
} catch (error) {
|
|
console.error(`处理缓存数据失败 (${level}):`, error);
|
|
reject(error);
|
|
} finally {
|
|
// 无论成功失败,都从活跃请求列表中移除
|
|
activeDataRefreshRequests.delete(level);
|
|
}
|
|
});
|
|
} else {
|
|
console.log(`📡 从服务器获取数据: ${level}`);
|
|
// 直接使用CustomerDataCache.refreshLevelData方法,避免重复代码
|
|
window.customerCache.refreshLevelData(level)
|
|
.then(freshData => {
|
|
// 使用requestAnimationFrame确保渲染在下一帧进行
|
|
requestAnimationFrame(() => {
|
|
try {
|
|
const processedData = optimizedProcessFilteredCustomers(freshData, level);
|
|
// 缓存加载完毕后,触发最近客户的刷新,使所有客户数据在同一时间点渲染
|
|
if (level === 'all' && typeof refreshRecentCustomers === 'function') {
|
|
refreshRecentCustomers(processedData || freshData);
|
|
}
|
|
resolve(processedData || freshData);
|
|
} catch (error) {
|
|
console.error(`处理新获取的数据失败 (${level}):`, error);
|
|
reject(error);
|
|
} finally {
|
|
activeDataRefreshRequests.delete(level);
|
|
}
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error(`刷新客户数据失败 (${level}):`, error);
|
|
|
|
// 尝试使用降级策略:如果请求'all'等级失败,尝试从其他缓存的等级数据合并
|
|
if (level === 'all' && window.customerCache) {
|
|
console.log(`🔄 尝试使用降级策略,从其他等级缓存合并数据`);
|
|
try {
|
|
const mergedData = [];
|
|
const levelsToMerge = ['important', 'normal', 'low-value', 'logistics', 'unclassified', 'public-sea'];
|
|
|
|
levelsToMerge.forEach(l => {
|
|
const levelData = window.customerCache.get(l);
|
|
if (levelData && Array.isArray(levelData)) {
|
|
mergedData.push(...levelData);
|
|
}
|
|
});
|
|
|
|
if (mergedData.length > 0) {
|
|
console.log(`✅ 降级策略成功,合并了${mergedData.length}条数据`);
|
|
requestAnimationFrame(() => {
|
|
const processedData = optimizedProcessFilteredCustomers(mergedData, level);
|
|
// 缓存加载完毕后,触发最近客户的刷新,使所有客户数据在同一时间点渲染
|
|
if (level === 'all' && typeof refreshRecentCustomers === 'function') {
|
|
refreshRecentCustomers(processedData || mergedData);
|
|
}
|
|
resolve(processedData || mergedData);
|
|
activeDataRefreshRequests.delete(level);
|
|
});
|
|
return;
|
|
}
|
|
} catch (mergeError) {
|
|
console.error('降级策略失败:', mergeError);
|
|
}
|
|
}
|
|
|
|
// 错误处理:显示用户友好的错误提示,但不中断流程
|
|
const errorContainer = document.getElementById('customers-data-error');
|
|
if (errorContainer) {
|
|
errorContainer.textContent = `获取数据失败,请稍后重试`;
|
|
errorContainer.style.display = 'block';
|
|
// 3秒后自动隐藏错误信息
|
|
setTimeout(() => {
|
|
errorContainer.style.display = 'none';
|
|
}, 3000);
|
|
}
|
|
|
|
reject(error);
|
|
activeDataRefreshRequests.delete(level);
|
|
});
|
|
}
|
|
});
|
|
|
|
// 将此请求添加到活跃请求列表
|
|
activeDataRefreshRequests.set(level, refreshPromise);
|
|
|
|
// 设置超时处理,防止请求无限期挂起
|
|
const timeoutId = setTimeout(() => {
|
|
if (activeDataRefreshRequests.has(level)) {
|
|
console.warn(`⌛ 数据刷新请求超时 (${level})`);
|
|
activeDataRefreshRequests.delete(level);
|
|
}
|
|
}, 10000); // 10秒超时
|
|
|
|
// 清理超时计时器
|
|
refreshPromise.finally(() => {
|
|
clearTimeout(timeoutId);
|
|
});
|
|
|
|
return refreshPromise;
|
|
}
|
|
// 新增:应用当前时间筛选
|
|
function applyCurrentTimeFilter(level) {
|
|
if (!window.currentTimeFilter) {
|
|
// 如果没有设置时间筛选,使用默认的今天筛选
|
|
const today = new Date();
|
|
const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
|
|
window.currentTimeFilter = {
|
|
start: startOfToday,
|
|
end: today
|
|
};
|
|
}
|
|
|
|
const { start, end } = window.currentTimeFilter;
|
|
|
|
// 根据等级获取对应的客户数据
|
|
let customers = [];
|
|
switch (level) {
|
|
case 'important':
|
|
customers = importantCustomersData || [];
|
|
break;
|
|
case 'normal':
|
|
customers = normalCustomersData || [];
|
|
break;
|
|
case 'low':
|
|
customers = lowCustomersData || [];
|
|
break;
|
|
case 'logistics':
|
|
customers = logisticsCustomersData || [];
|
|
break;
|
|
case 'unclassified':
|
|
customers = unclassifiedCustomersData || [];
|
|
break;
|
|
case 'company-sea-pools':
|
|
customers = companySeaPoolsCustomersData || [];
|
|
break;
|
|
case 'organization-sea-pools':
|
|
customers = organizationSeaPoolsCustomersData || [];
|
|
break;
|
|
case 'department-sea-pools':
|
|
customers = departmentSeaPoolsCustomersData || [];
|
|
break;
|
|
case 'all':
|
|
customers = [
|
|
...(importantCustomersData || []),
|
|
...(normalCustomersData || []),
|
|
...(lowCustomersData || []),
|
|
...(logisticsCustomersData || []),
|
|
...(unclassifiedCustomersData || [])
|
|
];
|
|
break;
|
|
}
|
|
|
|
// 应用时间筛选
|
|
const filteredCustomers = customers.filter(customer => {
|
|
const timeField = customer.updated_at || customer.created_at;
|
|
if (!timeField) return false;
|
|
|
|
const customerTime = new Date(timeField);
|
|
return customerTime >= start && customerTime <= end;
|
|
});
|
|
|
|
// 更新显示
|
|
if (level === 'all') {
|
|
updateAllCustomersPagination(filteredCustomers);
|
|
} else {
|
|
renderLevelTable(level, filteredCustomers);
|
|
updateLevelPaginationInfo(level);
|
|
}
|
|
}
|
|
// 新增:根据登录信息过滤客户数据
|
|
function filterCustomersByLoginInfo(customers, loginInfo, level) {
|
|
console.log('根据登录信息过滤客户数据:', { loginInfo, level });
|
|
|
|
return customers.filter(customer => {
|
|
// 公司公海池:所有人都能看到
|
|
if (level === 'company-sea-pools') {
|
|
return customer.level === 'company-sea-pools';
|
|
}
|
|
|
|
// 部门公海池:只显示登录用户所在部门的公海池
|
|
if (level === 'department-sea-pools') {
|
|
return customer.level === 'department-sea-pools' &&
|
|
customer.managerdepartment === loginInfo.managerdepartment;
|
|
}
|
|
|
|
// 组织公海池:只显示登录用户所在组织的公海池
|
|
if (level === 'organization-sea-pools') {
|
|
return customer.level === 'organization-sea-pools' &&
|
|
customer.organization === loginInfo.organization;
|
|
}
|
|
|
|
// 其他客户等级:正常显示
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// 优化普通客户的数据处理和分页
|
|
function processFilteredCustomers(customers, level) {
|
|
return optimizedProcessFilteredCustomers(customers, level);
|
|
}
|
|
|
|
|
|
|
|
// 在 DOMContentLoaded 事件中添加默认时间筛选
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('初始化时间筛选器...');
|
|
|
|
console.log('时间筛选器初始化完成,默认不应用筛选');
|
|
});
|
|
|
|
// 时间筛选器功能
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
console.log('初始化时间筛选器...');
|
|
|
|
// 获取DOM元素
|
|
const filterTypeSelect = document.getElementById('filter-type-select');
|
|
const filterTypeDropdown = document.getElementById('filter-type-dropdown');
|
|
const filterTypeDisplay = document.getElementById('filter-type-display');
|
|
const dynamicOptionsSelect = document.getElementById('dynamic-options-select');
|
|
const dynamicOptionsDropdown = document.getElementById('dynamic-options-dropdown');
|
|
const dynamicOptionDisplay = document.getElementById('dynamic-option-display');
|
|
const customDateInputs = document.getElementById('custom-date-inputs');
|
|
const startDateInput = document.getElementById('start-date');
|
|
const endDateInput = document.getElementById('end-date');
|
|
const applyFilterBtn = document.getElementById('apply-filter');
|
|
const resetFilterBtn = document.getElementById('reset-filter');
|
|
|
|
// 当前筛选状态
|
|
let currentFilterType = 'dynamic'; // 'dynamic' 或 'custom'
|
|
let currentDynamicOption = 'today'; // 默认今天
|
|
let currentStartDate = null;
|
|
let currentEndDate = null;
|
|
|
|
// 初始化默认筛选 - 今天00:00到当前时间
|
|
initializeDefaultFilter();
|
|
|
|
// 筛选类型选择事件
|
|
filterTypeSelect.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
toggleDropdown(filterTypeDropdown);
|
|
});
|
|
|
|
// 动态选项选择事件
|
|
dynamicOptionsSelect.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
toggleDropdown(dynamicOptionsDropdown);
|
|
});
|
|
|
|
// 筛选类型选项点击事件
|
|
filterTypeDropdown.querySelectorAll('.cascader-option').forEach(option => {
|
|
option.addEventListener('click', function () {
|
|
const value = this.getAttribute('data-value');
|
|
filterTypeDisplay.textContent = this.textContent;
|
|
currentFilterType = value;
|
|
|
|
// 根据筛选类型显示/隐藏相关元素
|
|
if (value === 'dynamic') {
|
|
dynamicOptionsSelect.style.display = 'block';
|
|
customDateInputs.style.display = 'none';
|
|
} else {
|
|
dynamicOptionsSelect.style.display = 'none';
|
|
customDateInputs.style.display = 'flex';
|
|
}
|
|
|
|
closeAllDropdowns();
|
|
console.log(`筛选类型已更改为: ${value}`);
|
|
});
|
|
});
|
|
|
|
// 动态选项点击事件
|
|
dynamicOptionsDropdown.querySelectorAll('.cascader-option').forEach(option => {
|
|
option.addEventListener('click', function() {
|
|
const value = this.getAttribute('data-value');
|
|
dynamicOptionDisplay.textContent = this.textContent;
|
|
currentDynamicOption = value;
|
|
closeAllDropdowns();
|
|
console.log(`动态筛选选项已更改为: ${value}`);
|
|
// 移除了自动筛选的调用
|
|
});
|
|
});
|
|
|
|
// 应用筛选按钮点击事件
|
|
applyFilterBtn.addEventListener('click', function() {
|
|
console.log('应用时间筛选...');
|
|
|
|
// 根据筛选类型获取时间范围
|
|
let startDate, endDate;
|
|
|
|
if (currentFilterType === 'dynamic') {
|
|
// 动态筛选
|
|
const dateRange = getDynamicDateRange(currentDynamicOption);
|
|
startDate = dateRange.start;
|
|
endDate = dateRange.end;
|
|
} else {
|
|
// 自定义筛选
|
|
startDate = new Date(startDateInput.value);
|
|
endDate = new Date(endDateInput.value);
|
|
|
|
// 验证日期
|
|
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
|
|
alert('请选择有效的开始和结束时间');
|
|
return;
|
|
}
|
|
|
|
if (startDate > endDate) {
|
|
alert('开始时间不能晚于结束时间');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 保存当前筛选条件
|
|
window.currentTimeFilter = {
|
|
start: startDate,
|
|
end: endDate
|
|
};
|
|
|
|
console.log(`筛选时间范围: ${startDate.toLocaleString()} - ${endDate.toLocaleString()}`);
|
|
|
|
// 执行筛选
|
|
applyTimeFilter(startDate, endDate);
|
|
});
|
|
|
|
// 重置筛选按钮点击事件
|
|
resetFilterBtn.addEventListener('click', function() {
|
|
console.log('重置时间筛选...');
|
|
});
|
|
|
|
// 刷新按钮点击事件
|
|
const refreshDataBtn = document.getElementById('refresh-data');
|
|
refreshDataBtn.addEventListener('click', async function() {
|
|
console.log('刷新数据...');
|
|
// 显示加载状态
|
|
this.disabled = true;
|
|
this.textContent = '刷新中...';
|
|
|
|
try {
|
|
// 清除缓存数据
|
|
if (window.customerCache && typeof window.customerCache.clear === 'function') {
|
|
window.customerCache.clear();
|
|
console.log('缓存已清除');
|
|
}
|
|
|
|
// 重新加载所有数据
|
|
if (window.customerCache && typeof window.customerCache.preloadAllLevels === 'function') {
|
|
await window.customerCache.preloadAllLevels();
|
|
console.log('数据重新加载完成');
|
|
|
|
// 获取最新数据
|
|
const allCustomers = window.customerCache.get('all') || [];
|
|
if (allCustomers.length > 0) {
|
|
allCustomersData = allCustomers;
|
|
console.log('全局数据已更新,数量:', allCustomersData.length);
|
|
}
|
|
}
|
|
|
|
// 获取当前活跃的标签页
|
|
const activeLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'all';
|
|
|
|
// 刷新显示数据
|
|
if (typeof refreshCustomerData === 'function') {
|
|
refreshCustomerData(activeLevel);
|
|
console.log('数据显示已刷新');
|
|
}
|
|
|
|
// 显示刷新成功消息
|
|
showResetMessage('数据刷新成功');
|
|
|
|
} catch (error) {
|
|
console.error('刷新数据失败:', error);
|
|
// 显示错误消息
|
|
showResetMessage('数据刷新失败,请重试', true);
|
|
} finally {
|
|
// 恢复按钮状态
|
|
this.disabled = false;
|
|
this.textContent = '刷新';
|
|
}
|
|
|
|
// 清空筛选条件
|
|
window.currentTimeFilter = null;
|
|
|
|
// 🔥 关键修复:清除筛选后的数据缓存,确保重置后使用原始数据
|
|
window.filteredCustomersData = null;
|
|
|
|
// 重置筛选器状态
|
|
currentFilterType = 'dynamic';
|
|
currentDynamicOption = 'today';
|
|
currentStartDate = null;
|
|
currentEndDate = null;
|
|
|
|
// 重置UI显示
|
|
filterTypeDisplay.textContent = '动态筛选';
|
|
dynamicOptionDisplay.textContent = '今天';
|
|
dynamicOptionsSelect.style.display = 'block';
|
|
customDateInputs.style.display = 'none';
|
|
|
|
// 清空日期输入框
|
|
startDateInput.value = '';
|
|
endDateInput.value = '';
|
|
|
|
// 重新加载当前等级的数据,显示所有数据
|
|
const currentLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'all';
|
|
console.log(`重置筛选,重新加载等级: ${currentLevel} 的数据`);
|
|
|
|
// 显示加载状态
|
|
const loadingIndicator = document.getElementById('customers-loading-indicator');
|
|
if (loadingIndicator) {
|
|
loadingIndicator.style.display = 'block';
|
|
}
|
|
|
|
// 重新加载数据
|
|
refreshCustomerData(currentLevel).then(() => {
|
|
console.log('时间筛选已重置,显示所有数据');
|
|
// 隐藏加载状态
|
|
if (loadingIndicator) {
|
|
loadingIndicator.style.display = 'none';
|
|
}
|
|
|
|
// 显示重置成功消息
|
|
showResetMessage();
|
|
}).catch(error => {
|
|
console.error('重置筛选时刷新数据失败:', error);
|
|
// 隐藏加载状态
|
|
if (loadingIndicator) {
|
|
loadingIndicator.style.display = 'none';
|
|
}
|
|
});
|
|
});
|
|
|
|
// 显示重置成功消息
|
|
function showResetMessage() {
|
|
const message = document.createElement('div');
|
|
message.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: rgba(108, 117, 125, 0.9);
|
|
color: white;
|
|
padding: 10px 15px;
|
|
border-radius: 5px;
|
|
z-index: 10000;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
`;
|
|
message.textContent = '时间筛选已重置,显示所有数据';
|
|
document.body.appendChild(message);
|
|
|
|
setTimeout(() => {
|
|
if (document.body.contains(message)) {
|
|
document.body.removeChild(message);
|
|
}
|
|
}, 3000);
|
|
}
|
|
|
|
// 点击页面其他区域关闭下拉框
|
|
document.addEventListener('click', function () {
|
|
closeAllDropdowns();
|
|
});
|
|
|
|
// 初始化默认筛选
|
|
function initializeDefaultFilter() {
|
|
console.log('初始化默认时间筛选(北京时间 UTC+8)...');
|
|
|
|
// 设置默认显示为今天(北京时间),并应用筛选
|
|
const now = new Date();
|
|
const beijingNow = new Date(now.getTime() + (8 * 60 * 60 * 1000));
|
|
const startOfToday = new Date(beijingNow.getFullYear(), beijingNow.getMonth(), beijingNow.getDate(), 0, 0, 0);
|
|
const endOfToday = new Date(beijingNow.getFullYear(), beijingNow.getMonth(), beijingNow.getDate(), 23, 59, 59, 999);
|
|
|
|
// 保存时间筛选条件
|
|
window.currentTimeFilter = {
|
|
start: startOfToday,
|
|
end: endOfToday
|
|
};
|
|
|
|
// 应用筛选
|
|
applyTimeFilter(startOfToday, endOfToday);
|
|
|
|
// 更新日期输入框显示
|
|
startDateInput.value = formatDateForInput(startOfToday);
|
|
endDateInput.value = formatDateForInput(endOfToday);
|
|
|
|
console.log('时间筛选器初始化完成,默认应用今天的筛选(北京时间):', { start: startOfToday, end: endOfToday });
|
|
}
|
|
|
|
|
|
// 切换下拉框显示状态
|
|
function toggleDropdown(dropdown) {
|
|
const isActive = dropdown.classList.contains('active');
|
|
closeAllDropdowns();
|
|
|
|
if (!isActive) {
|
|
dropdown.classList.add('active');
|
|
}
|
|
}
|
|
|
|
// 关闭所有下拉框
|
|
function closeAllDropdowns() {
|
|
filterTypeDropdown.classList.remove('active');
|
|
dynamicOptionsDropdown.classList.remove('active');
|
|
}
|
|
|
|
|
|
// 获取动态筛选的时间范围
|
|
function getDynamicDateRange(option) {
|
|
// 转换为北京时间(UTC+8)
|
|
const now = new Date();
|
|
const beijingNow = new Date(now.getTime() + (8 * 60 * 60 * 1000));
|
|
let start, end;
|
|
|
|
switch (option) {
|
|
case 'today':
|
|
// 今天(北京时间):00:00 到 23:59:59
|
|
start = new Date(beijingNow.getFullYear(), beijingNow.getMonth(), beijingNow.getDate(), 0, 0, 0);
|
|
end = new Date(beijingNow.getFullYear(), beijingNow.getMonth(), beijingNow.getDate(), 23, 59, 59, 999);
|
|
break;
|
|
case 'yesterday':
|
|
// 昨天(北京时间):00:00 到 23:59:59
|
|
start = new Date(beijingNow);
|
|
start.setDate(beijingNow.getDate() - 1);
|
|
start.setHours(0, 0, 0, 0);
|
|
end = new Date(start);
|
|
end.setHours(23, 59, 59, 999);
|
|
break;
|
|
case 'dayBeforeYesterday':
|
|
// 前天(北京时间):00:00 到 23:59:59
|
|
start = new Date(beijingNow);
|
|
start.setDate(beijingNow.getDate() - 2);
|
|
start.setHours(0, 0, 0, 0);
|
|
end = new Date(start);
|
|
end.setHours(23, 59, 59, 999);
|
|
break;
|
|
case 'threeDaysAgo':
|
|
// 大前天(北京时间):00:00 到 23:59:59
|
|
start = new Date(beijingNow);
|
|
start.setDate(beijingNow.getDate() - 3);
|
|
start.setHours(0, 0, 0, 0);
|
|
end = new Date(start);
|
|
end.setHours(23, 59, 59, 999);
|
|
break;
|
|
case 'lastWeek':
|
|
// 最近7天(北京时间):7天前 00:00 到当前时间
|
|
start = new Date(beijingNow);
|
|
start.setDate(beijingNow.getDate() - 7);
|
|
start.setHours(0, 0, 0, 0);
|
|
end = beijingNow;
|
|
break;
|
|
case 'lastMonth':
|
|
// 最近30天(北京时间):30天前 00:00 到当前时间
|
|
start = new Date(beijingNow);
|
|
start.setDate(beijingNow.getDate() - 30);
|
|
start.setHours(0, 0, 0, 0);
|
|
end = beijingNow;
|
|
break;
|
|
default:
|
|
// 默认今天(北京时间)
|
|
start = new Date(beijingNow.getFullYear(), beijingNow.getMonth(), beijingNow.getDate(), 0, 0, 0);
|
|
end = new Date(beijingNow.getFullYear(), beijingNow.getMonth(), beijingNow.getDate(), 23, 59, 59, 999);
|
|
}
|
|
|
|
console.log(`时间范围选项 "${option}"(北京时间):`, { start, end });
|
|
return { start, end };
|
|
}
|
|
|
|
// 自动执行最近客户刷新 -确保页面加载时自动更新
|
|
(function() {
|
|
console.log('注册最近客户自动刷新机制');
|
|
|
|
// 确保DOM加载完成后执行
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('DOM加载完成');
|
|
// 移除定时刷新,避免页面闪动
|
|
});
|
|
} else {
|
|
console.log('DOM已加载');
|
|
// 移除定时刷新,避免页面闪动
|
|
}
|
|
|
|
// 移除轮询机制,避免页面闪动
|
|
|
|
console.log('最近客户自动刷新机制注册完成');
|
|
})();
|
|
|
|
// 修复初始化函数,确保时间筛选器正确设置
|
|
function initTimeFilter() {
|
|
console.log('初始化时间筛选器...');
|
|
|
|
// 清空筛选条件,不设置默认筛选
|
|
window.currentTimeFilter = null;
|
|
|
|
// 清空日期输入框
|
|
document.getElementById('start-date').value = '';
|
|
document.getElementById('end-date').value = '';
|
|
|
|
console.log('时间筛选器初始化完成,默认不应用筛选');
|
|
}
|
|
|
|
// 修复数据刷新函数,确保时间筛选能正确应用
|
|
function refreshCustomerDataWithTimeFilter(level) {
|
|
console.log(`刷新客户数据并应用时间筛选,等级: ${level}`);
|
|
|
|
// 显示加载状态
|
|
const loadingIndicator = document.getElementById('customers-loading-indicator');
|
|
if (loadingIndicator) {
|
|
loadingIndicator.style.display = 'block';
|
|
}
|
|
|
|
// 先刷新数据
|
|
refreshCustomerData(level).then(() => {
|
|
// 延迟应用时间筛选,确保数据已加载
|
|
setTimeout(() => {
|
|
if (window.currentTimeFilter) {
|
|
const { start, end } = window.currentTimeFilter;
|
|
console.log('应用保存的时间筛选:', { start, end });
|
|
applyTimeFilter(start, end);
|
|
}
|
|
// 隐藏加载状态
|
|
if (loadingIndicator) {
|
|
loadingIndicator.style.display = 'none';
|
|
}
|
|
}, 500);
|
|
}).catch(error => {
|
|
console.error('刷新客户数据失败:', error);
|
|
// 隐藏加载状态
|
|
if (loadingIndicator) {
|
|
loadingIndicator.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
// 格式化日期为输入框需要的格式
|
|
function formatDateForInput(date) {
|
|
// 转换为北京时间(UTC+8)
|
|
const beijingDate = new Date(date.getTime() + (8 * 60 * 60 * 1000));
|
|
|
|
const year = beijingDate.getFullYear();
|
|
const month = String(beijingDate.getMonth() + 1).padStart(2, '0');
|
|
const day = String(beijingDate.getDate()).padStart(2, '0');
|
|
const hours = String(beijingDate.getHours()).padStart(2, '0');
|
|
const minutes = String(beijingDate.getMinutes()).padStart(2, '0');
|
|
|
|
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
}
|
|
|
|
// 修改时间筛选函数,使用缓存数据
|
|
function applyTimeFilter(startDate, endDate) {
|
|
console.log('🕒 应用时间筛选到客户数据...', { startDate, endDate });
|
|
|
|
// 获取当前显示的客户等级
|
|
const currentLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'all';
|
|
console.log(`当前客户等级: ${currentLevel}`);
|
|
|
|
// 从缓存获取数据
|
|
let customers = [];
|
|
|
|
switch (currentLevel) {
|
|
case 'important':
|
|
case 'normal':
|
|
case 'low':
|
|
case 'logistics':
|
|
case 'unclassified':
|
|
customers = [...(window.customerCache.get(currentLevel) || [])];
|
|
break;
|
|
case 'company-sea-pools':
|
|
case 'organization-sea-pools':
|
|
case 'department-sea-pools':
|
|
// 获取当前公海池等级的数据
|
|
const seaPoolData = window.customerCache.get(currentLevel) || [];
|
|
// 确保只保留对应公海池级别的客户
|
|
customers = seaPoolData.filter(customer => {
|
|
const standardizedLevel = standardizeCustomerLevel(customer.level);
|
|
return standardizedLevel === currentLevel;
|
|
});
|
|
console.log(`🌊 公海池数据预筛选:原始数量=${seaPoolData.length},筛选后数量=${customers.length},公海池类型=${currentLevel}`);
|
|
break;
|
|
case 'all':
|
|
// 全部客户从缓存中获取所有等级数据
|
|
const allLevels = ['important', 'normal', 'low-value', 'logistics', 'unclassified'];
|
|
customers = [];
|
|
allLevels.forEach(level => {
|
|
const levelData = window.customerCache.get(level) || [];
|
|
customers.push(...levelData);
|
|
});
|
|
break;
|
|
default:
|
|
customers = [];
|
|
}
|
|
|
|
console.log(`筛选前客户数量: ${customers.length}`);
|
|
|
|
// 先对数据进行排序
|
|
const sortedCustomers = customers.sort((a, b) => {
|
|
const aTime = new Date(a.updated_at || a.created_at);
|
|
const bTime = new Date(b.updated_at || b.created_at);
|
|
|
|
// 转换为北京时间进行比较
|
|
const beijingATime = new Date(aTime.getTime() + (8 * 60 * 60 * 1000));
|
|
const beijingBTime = new Date(bTime.getTime() + (8 * 60 * 60 * 1000));
|
|
|
|
return beijingBTime - beijingATime;
|
|
});
|
|
|
|
// 筛选客户数据
|
|
const filteredCustomers = sortedCustomers.filter(customer => {
|
|
if (!customer) return false;
|
|
|
|
const timeField = customer.updated_at || customer.created_at;
|
|
if (!timeField) return false;
|
|
|
|
let customerTime;
|
|
try {
|
|
if (typeof timeField === 'string') {
|
|
if (timeField.includes(' ') && !timeField.includes('Z')) {
|
|
customerTime = new Date(timeField.replace(' ', 'T'));
|
|
} else {
|
|
customerTime = new Date(timeField);
|
|
}
|
|
} else if (typeof timeField === 'number') {
|
|
customerTime = new Date(timeField);
|
|
} else {
|
|
customerTime = new Date(timeField);
|
|
}
|
|
|
|
if (isNaN(customerTime.getTime())) return false;
|
|
|
|
// 转换为北京时间进行比较
|
|
const beijingCustomerTime = new Date(customerTime.getTime() + (8 * 60 * 60 * 1000));
|
|
|
|
return beijingCustomerTime >= startDate && beijingCustomerTime <= endDate;
|
|
} catch (error) {
|
|
console.error('处理客户时间时出错:', error, customer);
|
|
return false;
|
|
}
|
|
});
|
|
|
|
console.log(`筛选后客户数量: ${filteredCustomers.length}`);
|
|
|
|
// 更新表格显示
|
|
if (currentLevel === 'all') {
|
|
updateAllCustomersPagination(filteredCustomers);
|
|
|
|
// 保存筛选后的数据到全局变量,以便分页切换时使用
|
|
if (!window.filteredCustomersData) {
|
|
window.filteredCustomersData = {};
|
|
}
|
|
window.filteredCustomersData[currentLevel] = filteredCustomers;
|
|
|
|
} else {
|
|
// 重要:应用时间筛选后,重置页码到第一页,确保显示正确的分页
|
|
switch (currentLevel) {
|
|
case 'important': importantCurrentPage = 1; break;
|
|
case 'normal': normalCurrentPage = 1; break;
|
|
case 'low': lowCurrentPage = 1; break;
|
|
case 'logistics': logisticsCurrentPage = 1; break;
|
|
case 'unclassified': unclassifiedCurrentPage = 1; break;
|
|
case 'company-sea-pools': companySeaPoolsCurrentPage = 1; break;
|
|
case 'organization-sea-pools': organizationSeaPoolsCurrentPage = 1; break;
|
|
case 'department-sea-pools': departmentSeaPoolsCurrentPage = 1; break;
|
|
}
|
|
|
|
// 保存筛选后的数据到全局变量,以便分页切换时使用
|
|
if (!window.filteredCustomersData) {
|
|
window.filteredCustomersData = {};
|
|
}
|
|
window.filteredCustomersData[currentLevel] = filteredCustomers;
|
|
|
|
// 渲染表格并传递筛选后的数据,renderLevelTable内部会调用updateLevelPaginationInfo并传递filteredCustomers
|
|
renderLevelTable(currentLevel, filteredCustomers);
|
|
// 移除多余的updateLevelPaginationInfo调用,因为renderLevelTable内部已经会调用并传递正确的数据
|
|
}
|
|
|
|
// 显示筛选结果信息
|
|
showFilterResultMessage(filteredCustomers.length, customers.length);
|
|
}
|
|
|
|
|
|
// 显示筛选结果信息
|
|
function showFilterResultMessage(filteredCount, totalCount) {
|
|
// 可以在这里添加一个短暂的消息提示
|
|
console.log(`时间筛选完成: 显示 ${filteredCount} 条 / 总计 ${totalCount} 条`);
|
|
|
|
// 可选:在页面上显示一个短暂的消息
|
|
const message = document.createElement('div');
|
|
message.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: rgba(76, 175, 80, 0.9);
|
|
color: white;
|
|
padding: 10px 15px;
|
|
border-radius: 5px;
|
|
z-index: 10000;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
`;
|
|
message.textContent = `时间筛选完成: 显示 ${filteredCount} 条记录`;
|
|
document.body.appendChild(message);
|
|
|
|
setTimeout(() => {
|
|
document.body.removeChild(message);
|
|
}, 3000);
|
|
}
|
|
|
|
console.log('时间筛选器初始化完成');
|
|
});
|
|
|
|
|
|
|
|
// 改进的缓存初始化 - 实现延迟加载和按需加载
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('🚀 启动客户数据缓存系统');
|
|
|
|
// 初始化缓存系统
|
|
setTimeout(() => {
|
|
window.customerCache.init();
|
|
|
|
// 只预加载重要客户数据和当前活跃标签页数据,其他数据按需加载
|
|
const currentActiveLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'important';
|
|
|
|
// 并行加载重要客户和当前活跃标签页数据
|
|
Promise.all([
|
|
window.customerCache.refreshLevelData('important'),
|
|
currentActiveLevel !== 'important' ? window.customerCache.refreshLevelData(currentActiveLevel) : Promise.resolve()
|
|
]).then(() => {
|
|
console.log('✅ 关键客户数据加载完成');
|
|
|
|
// 预加载完成后,尝试获取全部客户数据并刷新最近客户
|
|
const allCustomers = window.customerCache.get('all') || [];
|
|
if (allCustomers.length > 0 && typeof refreshRecentCustomers === 'function') {
|
|
console.log('预加载完成,触发最近客户数据刷新');
|
|
refreshRecentCustomers(allCustomers);
|
|
}
|
|
|
|
// 添加等级标签切换的按需加载事件
|
|
setupLevelTabsForOnDemandLoading();
|
|
|
|
// 显示初始缓存状态
|
|
const cacheStatus = window.customerCache.getCacheStatus();
|
|
console.log('初始缓存状态:', cacheStatus);
|
|
});
|
|
}, 500); // 减少延迟时间
|
|
|
|
// 添加缓存状态显示
|
|
setTimeout(() => {
|
|
addCacheStatusDisplay();
|
|
}, 1500);
|
|
});
|
|
|
|
// 设置等级标签的按需加载
|
|
function setupLevelTabsForOnDemandLoading() {
|
|
const levelTabs = document.querySelectorAll('.level-tab');
|
|
levelTabs.forEach(tab => {
|
|
tab.addEventListener('click', function() {
|
|
const level = this.getAttribute('data-level');
|
|
|
|
// 检查该等级的数据是否已缓存
|
|
const cacheStatus = window.customerCache.getCacheStatus();
|
|
if (!cacheStatus[level] || cacheStatus[level].isExpired) {
|
|
console.log(`🔄 按需加载 ${level} 客户数据`);
|
|
window.customerCache.refreshLevelData(level);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 改进的自动刷新机制
|
|
function initAutoRefresh() {
|
|
console.log('初始化自动刷新功能');
|
|
|
|
// 设置定时刷新 - 每2分钟刷新一次
|
|
setInterval(() => {
|
|
console.log('🕒 执行定时缓存刷新');
|
|
const cacheStatus = window.customerCache.getCacheStatus();
|
|
|
|
// 只刷新过期的缓存
|
|
Object.keys(cacheStatus).forEach(level => {
|
|
if (cacheStatus[level].isExpired) {
|
|
console.log(`🔄 刷新过期缓存: ${level}`);
|
|
window.customerCache.refreshLevelData(level);
|
|
}
|
|
});
|
|
}, 2 * 60 * 1000); // 2分钟
|
|
|
|
// 监听页面可见性变化,当页面重新可见时刷新数据
|
|
document.addEventListener('visibilitychange', function() {
|
|
if (!document.hidden) {
|
|
console.log('📄 页面重新可见,刷新数据');
|
|
refreshCurrentPageData();
|
|
}
|
|
});
|
|
}
|
|
|
|
// ==================== CSS 样式 ====================
|
|
const cacheStyles = `
|
|
<style>
|
|
.cache-status-item {
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
|
|
.cache-valid {
|
|
background-color: rgba(76, 175, 80, 0.1);
|
|
}
|
|
|
|
.cache-expired {
|
|
background-color: rgba(255, 152, 0, 0.1);
|
|
}
|
|
|
|
.cache-status-item:hover {
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
#cacheStatusBtn {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 16px;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
#cacheStatusBtn:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
|
}
|
|
</style>
|
|
`;
|
|
|
|
// 添加样式到页面
|
|
document.head.insertAdjacentHTML('beforeend', cacheStyles);
|
|
|
|
|
|
// 添加刷新按钮事件监听
|
|
function addRefreshButtons() {
|
|
// 为客户管理页面添加刷新按钮
|
|
const customersSearchBar = document.querySelector('#customers-page .search-bar');
|
|
if (customersSearchBar && !document.getElementById('manualRefreshBtn')) {
|
|
const refreshBtn = document.createElement('button');
|
|
refreshBtn.id = 'manualRefreshBtn';
|
|
refreshBtn.className = 'primary-btn';
|
|
refreshBtn.innerHTML = '<i class="fas fa-sync-alt"></i> 刷新';
|
|
refreshBtn.style.marginLeft = '10px';
|
|
|
|
refreshBtn.addEventListener('click', function () {
|
|
refreshCustomersData();
|
|
// 添加旋转动画
|
|
const icon = this.querySelector('i');
|
|
icon.style.transition = 'transform 0.5s';
|
|
icon.style.transform = 'rotate(360deg)';
|
|
setTimeout(() => {
|
|
icon.style.transform = 'rotate(0deg)';
|
|
}, 500);
|
|
});
|
|
|
|
customersSearchBar.appendChild(refreshBtn);
|
|
}
|
|
}
|
|
|
|
|
|
// 确保standardizeCustomerLevel函数存在
|
|
function standardizeCustomerLevel(level) {
|
|
if (!level && level !== 0) return 'unclassified';
|
|
|
|
// 处理数字类型
|
|
if (typeof level === 'number') {
|
|
const levelMap = {0: 'important', 1: 'normal', 2: 'low-value', 3: 'logistics'};
|
|
return levelMap[level] || 'unclassified';
|
|
}
|
|
|
|
// 处理字符串类型
|
|
const levelStr = String(level).toLowerCase();
|
|
|
|
if (levelStr.includes('important') || levelStr.includes('vip') || levelStr.includes('重要')) {
|
|
return 'important';
|
|
} else if (levelStr.includes('regular') || levelStr.includes('normal') || levelStr.includes('普通')) {
|
|
return 'normal';
|
|
} else if (levelStr.includes('low') || levelStr.includes('低')) {
|
|
return 'low-value';
|
|
} else if (levelStr.includes('logistics') || levelStr.includes('物流')) {
|
|
return 'logistics';
|
|
}
|
|
|
|
return 'unclassified';
|
|
}
|
|
|
|
// 修改控制面板最近客户的渲染逻辑
|
|
function refreshRecentCustomers(allCustomers) {
|
|
// 记录这是第几次调用(用于调试)
|
|
if (!window._recentCustomersRefreshCount) {
|
|
window._recentCustomersRefreshCount = 0;
|
|
}
|
|
window._recentCustomersRefreshCount++;
|
|
console.log(`========== 开始刷新最近客户数据 (第${window._recentCustomersRefreshCount}次调用) ==========`);
|
|
console.log('函数被调用,调用栈:', new Error().stack?.split('\n').slice(1, 5).join('\n'));
|
|
console.log('函数输入参数检查:', {
|
|
type: typeof allCustomers,
|
|
isArray: Array.isArray(allCustomers),
|
|
length: allCustomers?.length || 0
|
|
});
|
|
|
|
// 主动数据加载策略
|
|
function proactiveDataLoading() {
|
|
console.log('执行主动数据加载策略...');
|
|
const loadingAttempts = [];
|
|
|
|
// 尝试各种可能的数据加载函数
|
|
const loadFunctions = [
|
|
{name: 'refreshCustomersData', fn: () => typeof refreshCustomersData === 'function' && refreshCustomersData()},
|
|
{name: 'loadAllCustomers', fn: () => typeof loadAllCustomers === 'function' && loadAllCustomers()},
|
|
{name: 'getAllCustomers', fn: () => typeof getAllCustomers === 'function' && getAllCustomers()},
|
|
{name: 'initCustomerData', fn: () => typeof initCustomerData === 'function' && initCustomerData()},
|
|
{name: 'reloadCustomers', fn: () => typeof reloadCustomers === 'function' && reloadCustomers()}
|
|
];
|
|
|
|
// 执行所有可能的数据加载函数
|
|
loadFunctions.forEach(func => {
|
|
try {
|
|
console.log(`尝试调用${func.name}...`);
|
|
const result = func.fn();
|
|
if (result !== false && result !== null && result !== undefined) {
|
|
loadingAttempts.push({name: func.name, success: true});
|
|
}
|
|
} catch (e) {
|
|
console.error(`调用${func.name}失败:`, e);
|
|
loadingAttempts.push({name: func.name, success: false, error: e.message});
|
|
}
|
|
});
|
|
|
|
console.log('数据加载尝试结果:', loadingAttempts);
|
|
return loadingAttempts.some(attempt => attempt.success);
|
|
}
|
|
|
|
// 智能等待函数
|
|
function smartWait(delayMs, checkFn, maxAttempts = 3) {
|
|
return new Promise((resolve) => {
|
|
let attempts = 0;
|
|
|
|
function checkCondition() {
|
|
attempts++;
|
|
console.log(`智能等待检查 (${attempts}/${maxAttempts})...`);
|
|
|
|
if (checkFn()) {
|
|
console.log('条件满足,等待完成');
|
|
resolve(true);
|
|
} else if (attempts < maxAttempts) {
|
|
console.log(`条件未满足,${delayMs}ms后重试...`);
|
|
setTimeout(checkCondition, delayMs);
|
|
} else {
|
|
console.log(`达到最大尝试次数(${maxAttempts}),等待结束`);
|
|
resolve(false);
|
|
}
|
|
}
|
|
|
|
setTimeout(checkCondition, delayMs);
|
|
});
|
|
}
|
|
|
|
// 如果是首次调用,立即尝试加载数据
|
|
if (window._recentCustomersRefreshCount === 1) {
|
|
console.log('首次调用,立即触发数据加载...');
|
|
proactiveDataLoading();
|
|
}
|
|
|
|
// 根据需求,从指定的5种客户类型中获取数据
|
|
const targetLevels = ['important', 'normal', 'low-value', 'logistics', 'unclassified'];
|
|
console.log('需要获取的客户等级列表:', targetLevels);
|
|
|
|
let mergedCustomers = [];
|
|
|
|
// 1. 首先尝试从缓存获取数据 - 增强版
|
|
if (typeof customerCache !== 'undefined' && customerCache) {
|
|
console.log('检查customerCache是否可用');
|
|
// 如果缓存存在但为空,尝试手动触发数据加载
|
|
let cacheIsEmpty = true;
|
|
for (const level of targetLevels) {
|
|
if (customerCache[level] && (Array.isArray(customerCache[level]) && customerCache[level].length > 0 ||
|
|
(customerCache[level].data && customerCache[level].data.length > 0))) {
|
|
cacheIsEmpty = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 如果缓存为空,尝试手动调用可能的数据加载函数
|
|
if (cacheIsEmpty) {
|
|
console.log('缓存为空,尝试触发数据加载...');
|
|
const loadingSucceeded = proactiveDataLoading();
|
|
|
|
// 如果加载成功且是前几次调用,尝试等待缓存更新
|
|
if (loadingSucceeded && window._recentCustomersRefreshCount <= 3) {
|
|
console.log('数据加载成功,尝试等待缓存更新...');
|
|
|
|
// 创建一个检查函数来验证缓存是否已更新
|
|
const cacheUpdatedCheck = () => {
|
|
if (!customerCache) return false;
|
|
for (const level of targetLevels) {
|
|
if (customerCache[level] && (Array.isArray(customerCache[level]) && customerCache[level].length > 0 ||
|
|
(customerCache[level].data && customerCache[level].data.length > 0))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// 尝试等待缓存更新,如果成功则重新调用
|
|
smartWait(300, cacheUpdatedCheck, 3).then(updated => {
|
|
if (updated) {
|
|
console.log('缓存已更新,不再自动调用refreshRecentCustomers以避免页面闪动...');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
targetLevels.forEach(level => {
|
|
console.log(`获取等级: ${level}`);
|
|
let cachedData = null;
|
|
|
|
try {
|
|
// 尝试多种缓存结构获取方式
|
|
if (typeof customerCache.get === 'function') {
|
|
cachedData = customerCache.get(level);
|
|
}
|
|
|
|
// 如果缓存中没有,尝试从其他属性获取
|
|
if (!cachedData || !Array.isArray(cachedData)) {
|
|
if (customerCache[level] && customerCache[level].data && Array.isArray(customerCache[level].data)) {
|
|
cachedData = customerCache[level].data;
|
|
} else if (Array.isArray(customerCache[level])) {
|
|
cachedData = customerCache[level];
|
|
}
|
|
}
|
|
|
|
if (cachedData && Array.isArray(cachedData)) {
|
|
console.log(`从${level}等级获取到${cachedData.length}条数据`);
|
|
mergedCustomers = mergedCustomers.concat(cachedData);
|
|
}
|
|
} catch (e) {
|
|
console.error(`获取${level}等级数据时出错:`, e);
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log('从缓存合并后的数据总量:', mergedCustomers.length);
|
|
|
|
// 2. 如果缓存中没有数据,尝试使用传入的allCustomers
|
|
if (mergedCustomers.length === 0 && Array.isArray(allCustomers) && allCustomers.length > 0) {
|
|
console.log('使用传入的allCustomers参数');
|
|
// 从传入的数据中筛选指定等级的客户
|
|
const filteredByLevel = allCustomers.filter(customer => {
|
|
if (!customer) return false;
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
return targetLevels.includes(customerLevel);
|
|
});
|
|
console.log(`从传入数据中筛选出${filteredByLevel.length}条客户`);
|
|
mergedCustomers = filteredByLevel;
|
|
}
|
|
|
|
// 3. 尝试从其他全局变量获取客户数据 - 超级增强版
|
|
if (mergedCustomers.length === 0) {
|
|
console.log('尝试从全局变量获取客户数据...');
|
|
|
|
// 超级扩展的潜在数据源列表
|
|
const potentialDataSources = [
|
|
// 主要数据源
|
|
{name: 'allCustomersData', data: window.allCustomersData},
|
|
{name: 'customers', data: window.customers},
|
|
{name: 'customerData', data: window.customerData},
|
|
// 按等级分类的数据源
|
|
{name: 'importantCustomers', data: window.importantCustomers},
|
|
{name: 'normalCustomers', data: window.normalCustomers},
|
|
{name: 'lowValueCustomers', data: window.lowValueCustomers},
|
|
{name: 'logisticsCustomers', data: window.logisticsCustomers},
|
|
{name: 'unclassifiedCustomers', data: window.unclassifiedCustomers},
|
|
// 其他可能的数据源
|
|
{name: 'customerList', data: window.customerList},
|
|
{name: 'customerListData', data: window.customerListData},
|
|
{name: 'allCustomerData', data: window.allCustomerData},
|
|
{name: 'customerRecords', data: window.customerRecords},
|
|
{name: 'recentCustomerData', data: window.recentCustomerData},
|
|
// 新增数据源
|
|
{name: 'customerCacheData', data: window.customerCacheData},
|
|
{name: 'recentCustomers', data: window.recentCustomers},
|
|
{name: 'currentCustomers', data: window.currentCustomers},
|
|
{name: 'userCustomers', data: window.userCustomers},
|
|
{name: 'customerStore', data: window.customerStore},
|
|
{name: 'customerDatabase', data: window.customerDatabase}
|
|
];
|
|
|
|
potentialDataSources.forEach(source => {
|
|
try {
|
|
if (source.data && Array.isArray(source.data) && source.data.length > 0) {
|
|
console.log(`从全局变量${source.name}获取到${source.data.length}条数据`);
|
|
|
|
// 增强的筛选逻辑
|
|
const filteredByLevel = source.data.filter(customer => {
|
|
if (!customer || typeof customer !== 'object') return false;
|
|
|
|
// 检查是否有基本标识信息
|
|
const hasIdentifier = customer.id || customer.userId ||
|
|
customer.phoneNumber || customer.phone ||
|
|
customer.company || customer.name;
|
|
if (!hasIdentifier) return false;
|
|
|
|
// 增强的等级识别逻辑 - 检查所有可能的等级字段
|
|
const levelFields = ['level', 'type', 'customerLevel', 'customer_type', 'user_type'];
|
|
let customerLevel = null;
|
|
|
|
for (const field of levelFields) {
|
|
if (customer[field] !== undefined) {
|
|
customerLevel = standardizeCustomerLevel(customer[field]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 如果找到等级且在目标列表中,或者无法确定等级但有基本信息,就包含这条记录
|
|
return !customerLevel || targetLevels.includes(customerLevel);
|
|
});
|
|
|
|
console.log(`从${source.name}筛选出${filteredByLevel.length}条有效客户数据`);
|
|
mergedCustomers = mergedCustomers.concat(filteredByLevel);
|
|
}
|
|
} catch (e) {
|
|
console.error(`从全局变量${source.name}获取数据时出错:`, e);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 4. 尝试从页面上已渲染的数据中提取 - 超级增强版
|
|
if (mergedCustomers.length === 0) {
|
|
console.log('尝试从页面已渲染的数据中提取...');
|
|
|
|
try {
|
|
// 超级扩展的表格选择器
|
|
const tableSelectors = [
|
|
// ID选择器 - 按等级分类
|
|
'#important-customers', '#normal-customers',
|
|
'#low-customers', '#low-value-customers', '#logistics-customers',
|
|
'#unclassified-customers',
|
|
// ID选择器 - 通用表格
|
|
'#customers-table', '#all-customers-table', '#customer-list-table',
|
|
'#customerTable', '#recentCustomersTable', '#customersTable',
|
|
// 类选择器
|
|
'.customer-table', '.recent-customers-table', '.data-table',
|
|
'.grid-table', '.table-customers', '.table-recent-customers',
|
|
// 数据属性选择器
|
|
'[data-type="customers"]', '[data-table="customers"]',
|
|
'[data-role="customer-grid"]'
|
|
];
|
|
|
|
// 尝试多种表格查找策略
|
|
const tablesFound = new Set();
|
|
let totalExtracted = 0;
|
|
|
|
// 策略1: 使用选择器直接查找表格
|
|
console.log('使用选择器查找表格...');
|
|
tableSelectors.forEach(selector => {
|
|
try {
|
|
const tables = document.querySelectorAll(`${selector}, ${selector} table`);
|
|
tables.forEach(table => {
|
|
const tableId = table.id || table.getAttribute('data-id') ||
|
|
table.getAttribute('class') || 'anonymous-table';
|
|
|
|
if (!tablesFound.has(table)) {
|
|
tablesFound.add(table);
|
|
extractDataFromTable(table, tableId);
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.error(`使用选择器${selector}查找表格时出错:`, e);
|
|
}
|
|
});
|
|
|
|
// 策略2: 查找所有包含"客户"的表格
|
|
console.log('查找包含"客户"文本的表格...');
|
|
document.querySelectorAll('table').forEach(table => {
|
|
if (!tablesFound.has(table)) {
|
|
const tableText = table.textContent.toLowerCase();
|
|
if (tableText.includes('客户') || tableText.includes('customer')) {
|
|
tablesFound.add(table);
|
|
extractDataFromTable(table, 'customer-keyword-table');
|
|
}
|
|
}
|
|
});
|
|
|
|
// 从表格中提取数据的核心函数
|
|
function extractDataFromTable(table, tableId) {
|
|
console.log(`开始从表格(${tableId})提取数据`);
|
|
|
|
// 尝试找到tbody
|
|
let tbody = table.querySelector('tbody');
|
|
if (!tbody) {
|
|
// 如果没有tbody,使用表格本身作为父元素
|
|
tbody = table;
|
|
}
|
|
|
|
// 增强的行选择器策略
|
|
const rowSelectors = [
|
|
'tr[data-id], tr[data-phone], tr.customer-row, tr.data-row',
|
|
'tr:not(:has(th))', // 排除表头
|
|
'tr'
|
|
];
|
|
|
|
let rows = [];
|
|
for (const selector of rowSelectors) {
|
|
rows = tbody.querySelectorAll(selector);
|
|
if (rows.length > 0) {
|
|
console.log(`使用选择器${selector}找到${rows.length}行`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (rows.length === 0) {
|
|
console.log(`表格${tableId}中未找到数据行`);
|
|
return;
|
|
}
|
|
|
|
console.log(`表格${tableId}中找到${rows.length}行数据`);
|
|
|
|
// 智能提取每一行的数据
|
|
rows.forEach((row, rowIndex) => {
|
|
try {
|
|
// 跳过可能的表头行
|
|
if (rowIndex === 0 && (row.querySelector('th') ||
|
|
row.textContent.includes('公司名称') ||
|
|
row.textContent.includes('客户名称'))) {
|
|
console.log(`跳过表头行${rowIndex}`);
|
|
return;
|
|
}
|
|
|
|
// 创建客户对象 - 超级增强版提取
|
|
const customer = {
|
|
// ID提取 - 多种策略
|
|
id: row.getAttribute('data-id') ||
|
|
row.querySelector('[data-id]')?.getAttribute('data-id') ||
|
|
row.getAttribute('id') ||
|
|
row.querySelector('td:first-child')?.getAttribute('data-id') || '',
|
|
|
|
// 手机号提取 - 多种策略
|
|
phoneNumber: extractPhoneNumber(row),
|
|
|
|
// 等级提取
|
|
level: row.getAttribute('data-level') ||
|
|
row.querySelector('[data-level]')?.getAttribute('data-level') ||
|
|
extractLevelFromText(row.textContent) || '',
|
|
|
|
// 公司名称提取 - 增强策略
|
|
company: extractFromCell(row, 0, ['.company-name', '.company', '.organization']) || '',
|
|
|
|
// 客户昵称提取
|
|
nickName: extractFromCell(row, 1, ['.customer-name', '.name', '.nickname']) || '',
|
|
|
|
// 地区提取
|
|
region: extractFromCell(row, 2, ['.region', '.area', '.location']) || '',
|
|
|
|
// 联系人提取
|
|
contactPerson: extractFromCell(row, 3, ['.contact', '.contact-person']) || '',
|
|
|
|
// 联系方式提取(备用)
|
|
contactPhone: extractFromCell(row, 4, ['.contact-phone', '.tel']) || '',
|
|
|
|
// 智能提取更新时间
|
|
updated_at: extractTime(row) || Date.now(),
|
|
|
|
// 标记来源
|
|
source: 'dom',
|
|
domSource: tableId,
|
|
extractedAt: Date.now()
|
|
};
|
|
|
|
// 智能验证基本信息
|
|
if (isValidCustomer(customer)) {
|
|
console.log(`从表格${tableId}提取到有效客户:`, {
|
|
company: customer.company,
|
|
id: customer.id,
|
|
phone: customer.phoneNumber
|
|
});
|
|
mergedCustomers.push(customer);
|
|
totalExtracted++;
|
|
}
|
|
} catch (e) {
|
|
console.error(`从表格${tableId}的第${rowIndex}行提取数据时出错:`, e);
|
|
}
|
|
});
|
|
|
|
console.log(`从表格${tableId}成功提取到${totalExtracted}条有效客户数据`);
|
|
}
|
|
|
|
// 辅助函数:从单元格提取内容
|
|
function extractFromCell(row, cellIndex, selectors = []) {
|
|
// 先尝试直接获取第N个单元格
|
|
const cells = row.querySelectorAll('td');
|
|
if (cells.length > cellIndex) {
|
|
const cellContent = cells[cellIndex].textContent.trim();
|
|
if (cellContent && !isHeaderText(cellContent)) {
|
|
return cellContent;
|
|
}
|
|
}
|
|
|
|
// 然后尝试使用选择器
|
|
for (const selector of selectors) {
|
|
const element = row.querySelector(selector);
|
|
if (element) {
|
|
const content = element.textContent.trim();
|
|
if (content && !isHeaderText(content)) {
|
|
return content;
|
|
}
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
// 辅助函数:提取手机号
|
|
function extractPhoneNumber(row) {
|
|
// 先尝试从属性获取
|
|
const phoneAttr = row.getAttribute('data-phone') ||
|
|
row.querySelector('[data-phone]')?.getAttribute('data-phone') || '';
|
|
if (phoneAttr) return phoneAttr;
|
|
|
|
// 使用正则表达式从文本中提取
|
|
const text = row.textContent;
|
|
const phoneMatches = text.match(/1[3-9]\d{9}/g);
|
|
if (phoneMatches && phoneMatches.length > 0) {
|
|
return phoneMatches[0]; // 返回第一个找到的手机号
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
// 辅助函数:从文本中提取等级
|
|
function extractLevelFromText(text) {
|
|
const levelKeywords = {
|
|
'important': ['重要', 'VIP', '重要客户', 'vip'],
|
|
'normal': ['普通', '常规', 'regular', 'normal'],
|
|
'low-value': ['低价值', '小额', 'low', '小额客户'],
|
|
'logistics': ['物流', 'logistics'],
|
|
'unclassified': ['未分类', '未知', 'unclassified']
|
|
};
|
|
|
|
for (const [level, keywords] of Object.entries(levelKeywords)) {
|
|
for (const keyword of keywords) {
|
|
if (text.includes(keyword)) {
|
|
return level;
|
|
}
|
|
}
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
// 辅助函数:提取时间信息
|
|
function extractTime(row) {
|
|
// 尝试从属性获取
|
|
const timeAttr = row.getAttribute('data-time') ||
|
|
row.getAttribute('data-update-time') || '';
|
|
if (timeAttr) {
|
|
const time = new Date(timeAttr).getTime();
|
|
return isNaN(time) ? null : time;
|
|
}
|
|
|
|
// 尝试从文本中提取常见时间格式
|
|
const text = row.textContent;
|
|
const timeMatches = text.match(/\d{4}[-/]\d{1,2}[-/]\d{1,2}/);
|
|
if (timeMatches) {
|
|
const time = new Date(timeMatches[0]).getTime();
|
|
return isNaN(time) ? null : time;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// 辅助函数:验证是否为表头文本
|
|
function isHeaderText(text) {
|
|
const headerTexts = ['公司名称', '客户名称', '联系人', '电话',
|
|
'地区', '备注', '操作', 'Company', 'Customer'];
|
|
return headerTexts.some(header => text.includes(header));
|
|
}
|
|
|
|
// 辅助函数:验证客户数据是否有效
|
|
function isValidCustomer(customer) {
|
|
// 至少有一个标识字段
|
|
if (customer.id) return true;
|
|
if (customer.phoneNumber) return true;
|
|
if (customer.company && customer.company.length > 2 &&
|
|
!customer.company.includes('暂无') && !customer.company.includes('无数据')) {
|
|
return true;
|
|
}
|
|
if (customer.nickName && customer.nickName.length > 1 &&
|
|
!customer.nickName.includes('暂无') && !customer.nickName.includes('无数据')) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
console.log(`页面数据提取完成,共查找${tablesFound.size}个表格,提取${totalExtracted}条客户数据`);
|
|
|
|
} catch (e) {
|
|
console.error('从页面提取数据时出错:', e);
|
|
}
|
|
}
|
|
|
|
// 使用合并后的数据进行后续处理,并进行智能去重 - 增强版
|
|
console.log('开始智能去重处理,原始数据集长度:', mergedCustomers.length);
|
|
|
|
// 创建映射和键关联表用于智能去重
|
|
const uniqueCustomersMap = new Map();
|
|
const keyAssociations = new Map(); // 存储不同键之间的关联关系
|
|
|
|
// 智能去重处理
|
|
mergedCustomers.filter(c => c).forEach(customer => {
|
|
// 生成多种可能的唯一标识键
|
|
const keys = [];
|
|
|
|
// 1. ID键
|
|
if (customer.id) {
|
|
const idKey = `id:${customer.id}`;
|
|
keys.push(idKey);
|
|
}
|
|
|
|
// 2. 手机号键 - 标准化处理
|
|
if (customer.phoneNumber) {
|
|
// 标准化手机号,移除所有非数字字符
|
|
const normalizedPhone = customer.phoneNumber.replace(/[^0-9]/g, '');
|
|
const phoneKey = `phone:${normalizedPhone}`;
|
|
keys.push(phoneKey);
|
|
|
|
// 如果是完整手机号,也使用最后8位作为键,增加匹配几率
|
|
if (normalizedPhone.length >= 11) {
|
|
const shortPhoneKey = `phone_short:${normalizedPhone.slice(-8)}`;
|
|
keys.push(shortPhoneKey);
|
|
}
|
|
}
|
|
|
|
// 3. 公司名+昵称组合键
|
|
if (customer.company && customer.nickName) {
|
|
const companyKey = `company:${customer.company.trim()}`;
|
|
const comboKey = `combo:${customer.company.trim()}_${customer.nickName.trim()}`;
|
|
keys.push(companyKey);
|
|
keys.push(comboKey);
|
|
} else if (customer.company) {
|
|
// 仅公司名键
|
|
const companyKey = `company:${customer.company.trim()}`;
|
|
keys.push(companyKey);
|
|
}
|
|
|
|
// 如果没有任何有效键,使用随机ID但记录来源信息
|
|
if (keys.length === 0) {
|
|
const randomKey = `random:${Math.random().toString(36).substr(2, 9)}`;
|
|
keys.push(randomKey);
|
|
console.log('为无标识客户生成随机键');
|
|
}
|
|
|
|
// 查找是否已存在匹配的客户记录
|
|
let primaryKey = null;
|
|
for (const key of keys) {
|
|
if (keyAssociations.has(key)) {
|
|
primaryKey = keyAssociations.get(key);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 如果没有找到关联的主键,使用第一个键作为主键
|
|
if (!primaryKey) {
|
|
primaryKey = keys[0];
|
|
}
|
|
|
|
// 建立所有键到主键的关联
|
|
keys.forEach(key => {
|
|
keyAssociations.set(key, primaryKey);
|
|
});
|
|
|
|
// 获取或创建客户记录
|
|
if (uniqueCustomersMap.has(primaryKey)) {
|
|
// 已存在记录,执行智能合并
|
|
const existingCustomer = uniqueCustomersMap.get(primaryKey);
|
|
console.log(`合并客户数据,主键: ${primaryKey}`);
|
|
|
|
// 智能合并字段 - 保留非空值
|
|
const fieldsToMerge = ['id', 'phoneNumber', 'level', 'company', 'nickName',
|
|
'contactPerson', 'region', 'rowHtml', 'source'];
|
|
|
|
fieldsToMerge.forEach(field => {
|
|
if (customer[field] && !existingCustomer[field]) {
|
|
existingCustomer[field] = customer[field];
|
|
}
|
|
});
|
|
|
|
// 智能合并时间字段 - 保留更晚的时间
|
|
if (customer.updated_at && (!existingCustomer.updated_at ||
|
|
customer.updated_at > existingCustomer.updated_at)) {
|
|
existingCustomer.updated_at = customer.updated_at;
|
|
}
|
|
|
|
// 如果新数据来自更可靠的来源,优先保留
|
|
const sourcePriority = { 'cache': 3, 'api': 3, 'param': 2, 'dom': 1 };
|
|
const existingPriority = sourcePriority[existingCustomer.source] || 0;
|
|
const newPriority = sourcePriority[customer.source] || 0;
|
|
|
|
if (newPriority > existingPriority) {
|
|
existingCustomer.source = customer.source;
|
|
}
|
|
|
|
} else {
|
|
// 新记录,直接添加
|
|
uniqueCustomersMap.set(primaryKey, { ...customer });
|
|
}
|
|
});
|
|
|
|
// 转换为数组
|
|
const uniqueCustomers = Array.from(uniqueCustomersMap.values());
|
|
allCustomers = uniqueCustomers;
|
|
console.log('智能去重后的数据集长度:', allCustomers.length);
|
|
console.log('关联键数量:', keyAssociations.size);
|
|
|
|
// 过滤客户数据 - 只保留目标等级的客户
|
|
const filteredCustomers = (allCustomers || [])
|
|
.filter(customer => {
|
|
// 只保留有效的客户对象
|
|
if (!customer || typeof customer !== 'object') {
|
|
return false;
|
|
}
|
|
|
|
// 检查是否有基本的客户标识信息
|
|
const hasBasicInfo = customer.id || customer.userId ||
|
|
customer.phoneNumber || customer.phone ||
|
|
customer.company || customer.name;
|
|
|
|
if (!hasBasicInfo) {
|
|
return false;
|
|
}
|
|
|
|
// 检查客户等级 - 必须是目标等级之一
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
if (!targetLevels.includes(customerLevel)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
console.log('过滤后的有效客户数量:', filteredCustomers.length);
|
|
|
|
// 按照需求进行排序:首先按照修改时间筛选,如果修改时间不存在则按照创建时间筛选
|
|
console.log('开始按时间排序客户数据');
|
|
const sortedCustomers = filteredCustomers
|
|
.sort((a, b) => {
|
|
// 获取客户时间 - 严格按照需求规则:先updated_at,后created_at
|
|
const getCustomerTime = (customer) => {
|
|
try {
|
|
let timeValue = 0;
|
|
|
|
// 首先检查updated_at字段
|
|
if (customer.updated_at) {
|
|
const updateTime = customer.updated_at;
|
|
if (typeof updateTime === 'number') {
|
|
timeValue = updateTime;
|
|
} else if (typeof updateTime === 'string') {
|
|
const date = new Date(updateTime);
|
|
if (!isNaN(date.getTime())) {
|
|
timeValue = date.getTime();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 如果没有有效的updated_at,检查created_at字段
|
|
if (timeValue === 0 && customer.created_at) {
|
|
const createTime = customer.created_at;
|
|
if (typeof createTime === 'number') {
|
|
timeValue = createTime;
|
|
} else if (typeof createTime === 'string') {
|
|
const date = new Date(createTime);
|
|
if (!isNaN(date.getTime())) {
|
|
timeValue = date.getTime();
|
|
}
|
|
}
|
|
}
|
|
|
|
return timeValue;
|
|
} catch (e) {
|
|
console.error('时间解析错误:', e);
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
const timeA = getCustomerTime(a);
|
|
const timeB = getCustomerTime(b);
|
|
|
|
// 降序排列,最新的在前
|
|
return timeB - timeA;
|
|
})
|
|
.slice(0, 5); // 只取前5条最新数据
|
|
|
|
console.log('排序并截取前5条的客户数据数量:', sortedCustomers.length);
|
|
|
|
// 使用单独的变量名避免与其他可能的计数器冲突
|
|
if (typeof window._recentCustomersRefreshCount === 'undefined') {
|
|
window._recentCustomersRefreshCount = 0;
|
|
}
|
|
|
|
// 限制最大调用次数,防止无限增长
|
|
if (window._recentCustomersRefreshCount > 100) {
|
|
console.warn('检测到异常的刷新次数,可能存在无限循环,已重置计数器');
|
|
window._recentCustomersRefreshCount = 0;
|
|
}
|
|
|
|
window._recentCustomersRefreshCount++;
|
|
|
|
console.log(`========== 开始刷新最近客户数据 (第${window._recentCustomersRefreshCount}次调用) ==========`);
|
|
console.log('调用renderRecentCustomers渲染最近客户数据');
|
|
renderRecentCustomers(sortedCustomers);
|
|
console.log('========== 刷新最近客户数据完成 ==========');
|
|
|
|
// 移除智能重试机制,避免频繁刷新导致页面闪动
|
|
// 重置计数器,避免计数器无限增长
|
|
window._recentCustomersRefreshCount = 0;
|
|
}
|
|
|
|
// 移除了enhance-recent-customers.js相关的重写逻辑
|
|
|
|
// 自动执行最近客户刷新 - 确保页面加载时自动更新
|
|
(function() {
|
|
console.log('注册最近客户自动刷新机制');
|
|
|
|
// 缓存状态监控函数
|
|
function checkCacheStatus() {
|
|
const cacheStatus = {
|
|
exists: typeof customerCache !== 'undefined',
|
|
isObject: typeof customerCache === 'object',
|
|
hasKeys: customerCache ? Object.keys(customerCache).length > 0 : false,
|
|
keyDetails: customerCache ? {} : null
|
|
};
|
|
|
|
// 检查各个等级的缓存状态
|
|
if (cacheStatus.exists && cacheStatus.isObject) {
|
|
['important', 'normal', 'low-value', 'logistics', 'unclassified'].forEach(level => {
|
|
const levelData = customerCache[level];
|
|
if (levelData) {
|
|
cacheStatus.keyDetails[level] = {
|
|
isArray: Array.isArray(levelData),
|
|
hasData: levelData.data ? Array.isArray(levelData.data) && levelData.data.length > 0 : false,
|
|
length: Array.isArray(levelData) ? levelData.length : (levelData.data ? levelData.data.length : 0)
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log('缓存状态检查:', cacheStatus);
|
|
return cacheStatus;
|
|
}
|
|
|
|
// 增强的初始化函数
|
|
function initializeRecentCustomers() {
|
|
console.log('开始初始化最近客户模块...');
|
|
|
|
// 立即检查缓存状态
|
|
checkCacheStatus();
|
|
|
|
// 如果数据加载函数可用,先尝试加载数据
|
|
if (typeof refreshCustomersData === 'function') {
|
|
console.log('检测到refreshCustomersData函数可用,先尝试加载基础数据...');
|
|
try {
|
|
refreshCustomersData();
|
|
} catch (e) {
|
|
console.error('尝试预先加载数据时出错:', e);
|
|
}
|
|
}
|
|
|
|
// 完全移除自动初始化调用,避免页面闪动
|
|
// 现在只在用户主动操作时才会触发刷新
|
|
|
|
// 移除延迟再次调用,避免页面闪动
|
|
}
|
|
|
|
// 确保DOM加载完成后执行初始化
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('DOM加载完成,立即初始化最近客户模块...');
|
|
initializeRecentCustomers();
|
|
});
|
|
} else {
|
|
console.log('DOM已加载,立即初始化最近客户模块...');
|
|
initializeRecentCustomers();
|
|
}
|
|
|
|
// 移除轮询机制,避免自动刷新导致客户数据一直闪动
|
|
|
|
console.log('最近客户自动刷新机制注册完成');
|
|
})();
|
|
|
|
// 修改等级映射
|
|
function renderCustomerList(level, customers) {
|
|
console.log(`渲染 ${level} 等级客户,数量:`, customers.length);
|
|
|
|
// 映射前端等级到后端等级
|
|
const levelMap = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'low': 'low-value',
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified',
|
|
'public-sea': 'public-sea'
|
|
};
|
|
|
|
if (level === 'all') {
|
|
// 全部客户需要分别渲染到各个等级表格,但不包括公海池
|
|
const nonPublicSeaLevels = Object.keys(levelMap).filter(key => key !== 'public-sea');
|
|
nonPublicSeaLevels.forEach(key => {
|
|
const backendLevel = levelMap[key];
|
|
const filtered = customers.filter(c => c.level === backendLevel);
|
|
console.log(`${key} 等级客户数量:`, filtered.length);
|
|
renderToLevelTable(key, filtered);
|
|
});
|
|
|
|
// 特别处理全部客户的分页显示
|
|
updateAllCustomersPagination(customers);
|
|
} else {
|
|
const backendLevel = levelMap[level] || level;
|
|
|
|
// 特别处理未分级客户:只包含明确标记为unclassified的客户
|
|
if (level === 'unclassified') {
|
|
const filtered = customers.filter(c => c.level === 'unclassified');
|
|
console.log(`未分级客户数量:`, filtered.length);
|
|
renderToLevelTable(level, filtered);
|
|
} else if (level === 'public-sea') {
|
|
// 公海池客户:只包含明确标记为public-sea的客户
|
|
const filtered = customers.filter(c => c.level === 'public-sea');
|
|
console.log(`公海池客户数量:`, filtered.length);
|
|
renderToLevelTable(level, filtered);
|
|
} else {
|
|
// 其他等级正常过滤
|
|
const filtered = customers.filter(c => c.level === backendLevel);
|
|
console.log(`${level} 等级客户数量:`, filtered.length);
|
|
renderToLevelTable(level, filtered);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 全部客户的分页逻辑
|
|
let allCustomersData = []; // 存储全部客户数据
|
|
let allCustomersPageSize = 10; // 每页10条
|
|
let allCustomersCurrentPage = 1; // 当前页
|
|
|
|
// 修复全部客户分页数据渲染
|
|
function updateAllCustomersPagination(customers) {
|
|
// 重置到第一页
|
|
allCustomersCurrentPage = 1;
|
|
|
|
// 确保传入的数据有效
|
|
if (!customers || !Array.isArray(customers)) {
|
|
console.error('全部客户数据无效:', customers);
|
|
allCustomersData = [];
|
|
} else {
|
|
allCustomersData = customers;
|
|
}
|
|
|
|
// 强制重新渲染
|
|
renderAllCustomersTable();
|
|
updateAllCustomersPaginationInfo();
|
|
|
|
console.log('全部客户分页更新完成', {
|
|
dataCount: allCustomersData.length,
|
|
currentPage: allCustomersCurrentPage
|
|
});
|
|
}
|
|
|
|
// 优化后的 renderAllCustomersTable 函数,使用 DocumentFragment 减少 DOM 操作
|
|
function renderAllCustomersTable() {
|
|
const targetTbody = document.getElementById('all-customers');
|
|
if (!targetTbody) {
|
|
console.error('全部客户表格体未找到');
|
|
return;
|
|
}
|
|
|
|
console.log('渲染全部客户表格,当前数据状态:', {
|
|
allCustomersDataLength: allCustomersData ? allCustomersData.length : 0,
|
|
hasCache: window.customerCache && window.customerCache.get ? true : false,
|
|
isFiltered: window.filteredCustomersData && window.filteredCustomersData['all'] !== undefined
|
|
});
|
|
|
|
// 重要修复:当应用了时间筛选后,即使数据为空也不应从缓存重新获取数据
|
|
// 只有在非筛选状态下,且allCustomersData为空时才尝试从缓存获取数据
|
|
if ((!allCustomersData || allCustomersData.length === 0) &&
|
|
window.customerCache && window.customerCache.get &&
|
|
(!window.filteredCustomersData || window.filteredCustomersData['all'] === undefined)) {
|
|
const cachedData = window.customerCache.get('all');
|
|
if (cachedData && Array.isArray(cachedData) && cachedData.length > 0) {
|
|
console.log('从缓存获取客户数据,数量:', cachedData.length);
|
|
allCustomersData = cachedData;
|
|
}
|
|
}
|
|
|
|
// 更严格的空数据检查
|
|
if (!allCustomersData || allCustomersData.length === 0) {
|
|
console.log('没有可用的客户数据,显示空状态');
|
|
targetTbody.innerHTML = '<tr><td colspan="9" style="text-align: center; color: #666;">暂无客户数据</td></tr>';
|
|
updateAllCustomersPaginationInfo();
|
|
return;
|
|
}
|
|
|
|
// 确保分页计算正确
|
|
const startIndex = (allCustomersCurrentPage - 1) * allCustomersPageSize;
|
|
const endIndex = Math.min(startIndex + allCustomersPageSize, allCustomersData.length);
|
|
|
|
// 确保当前页码有效
|
|
const totalPages = Math.ceil(allCustomersData.length / allCustomersPageSize);
|
|
if (allCustomersCurrentPage > totalPages) {
|
|
allCustomersCurrentPage = 1;
|
|
}
|
|
|
|
// 使用 DocumentFragment 批量创建 DOM 元素,减少重排重绘
|
|
const fragment = document.createDocumentFragment();
|
|
let hasData = false;
|
|
|
|
// 直接遍历数据范围,生成HTML字符串
|
|
let htmlString = '';
|
|
for (let i = startIndex; i < endIndex; i++) {
|
|
const customer = allCustomersData[i];
|
|
if (!customer) continue;
|
|
|
|
hasData = true;
|
|
|
|
// 提取并设置默认值
|
|
const company = customer.company || customer.name || '微信客户';
|
|
const region = customer.region || customer.address || '-';
|
|
const demand = customer.demand || customer.intention || '-';
|
|
const spec = customer.spec || customer.specification || '-';
|
|
const nickName = customer.nickName || customer.userName || customer.contact || '-';
|
|
const phoneNumber = customer.phoneNumber || customer.phone || customer.mobile || '-';
|
|
|
|
// 安全的时间格式化处理
|
|
let createdAt = '-';
|
|
let updatedAt = '-';
|
|
try {
|
|
if (customer.created_at) {
|
|
createdAt = typeof formatTime === 'function' ? formatTime(customer.created_at) : new Date(new Date(customer.created_at).getTime() + (8 * 60 * 60 * 1000)).toLocaleDateString();
|
|
}
|
|
if (customer.updated_at) {
|
|
updatedAt = typeof formatTime === 'function' ? formatTime(customer.updated_at) : new Date(customer.updated_at).toLocaleDateString();
|
|
}
|
|
} catch (e) {
|
|
console.error('时间格式化错误:', e);
|
|
}
|
|
|
|
const customerId = customer.id || customer.userId || phoneNumber || '';
|
|
|
|
console.log(`渲染客户 #${i+1}:`, company, phoneNumber, customerId);
|
|
|
|
// 生成操作按钮HTML - 只保留跟进按钮
|
|
const actionButtonHtml = `
|
|
<button class="action-btn follow-up-btn" data-company-id="${customerId}" data-phone="${phoneNumber}">跟进</button>
|
|
`;
|
|
|
|
// 直接拼接HTML字符串
|
|
htmlString += `
|
|
<tr data-id="${customerId}" data-phone="${phoneNumber}" data-level="all" class="customer-row">
|
|
<td>${company}</td>
|
|
<td>${region}</td>
|
|
<td>${demand}</td>
|
|
<td>${spec}</td>
|
|
<td>${nickName}</td>
|
|
<td>${phoneNumber}</td>
|
|
<td>${createdAt}</td>
|
|
<td>${updatedAt}</td>
|
|
<td class="action-cell">${actionButtonHtml}</td>
|
|
</tr>`;
|
|
}
|
|
|
|
// 清空目标表格
|
|
targetTbody.innerHTML = '';
|
|
|
|
// 设置表格内容
|
|
if (hasData && htmlString) {
|
|
console.log('渲染全部客户表格,行数:', htmlString.match(/<tr/g) ? htmlString.match(/<tr/g).length : 0);
|
|
|
|
// 简化实现:直接设置HTML,避免复杂的DOM操作
|
|
targetTbody.innerHTML = htmlString;
|
|
|
|
// 确保表格可见
|
|
const table = targetTbody.closest('table');
|
|
if (table) {
|
|
table.style.display = 'table';
|
|
}
|
|
|
|
console.log('全部客户表格内容已设置');
|
|
} else {
|
|
console.log('没有数据,显示空状态');
|
|
targetTbody.innerHTML = `<tr><td colspan="9" style="text-align: center; color: #666;">暂无客户数据</td></tr>`;
|
|
}
|
|
|
|
// 添加表格事件委托
|
|
const table = targetTbody.closest('table');
|
|
if (table) {
|
|
// 移除之前的事件监听器,避免重复绑定
|
|
table.removeEventListener('click', tableClickHandler);
|
|
|
|
// 添加新的事件监听器
|
|
table.addEventListener('click', tableClickHandler);
|
|
}
|
|
|
|
// 更新分页信息
|
|
updateAllCustomersPaginationInfo();
|
|
}
|
|
|
|
// 表格点击事件处理器 - 统一处理跟进和查看详情按钮
|
|
function tableClickHandler(e) {
|
|
const followBtn = e.target.closest('.follow-up-btn');
|
|
const detailBtn = e.target.closest('.view-details');
|
|
const row = e.target.closest('tr.customer-row');
|
|
|
|
if (followBtn) {
|
|
e.stopPropagation();
|
|
const customerId = followBtn.getAttribute('data-company-id');
|
|
const phone = followBtn.getAttribute('data-phone');
|
|
console.log('跟进按钮被点击:', { customerId, phone });
|
|
|
|
// 调用跟进功能
|
|
if (typeof showFollowUpInterface === 'function') {
|
|
showFollowUpInterface(customerId, phone);
|
|
} else {
|
|
// 如果跟进函数未定义,显示提示
|
|
createSimpleNotification('跟进功能正在实现中');
|
|
}
|
|
}
|
|
else if (detailBtn) {
|
|
e.stopPropagation();
|
|
const customerId = detailBtn.getAttribute('data-company-id');
|
|
const phone = detailBtn.getAttribute('data-phone');
|
|
console.log('查看详情按钮被点击:', { customerId, phone });
|
|
|
|
// 调用查看详情功能
|
|
if (typeof viewCustomerDetails === 'function') {
|
|
viewCustomerDetails(customerId, phone);
|
|
}
|
|
}
|
|
else if (row) {
|
|
const customerId = row.getAttribute('data-id');
|
|
const phone = row.getAttribute('data-phone');
|
|
console.log('客户行被点击:', { customerId, phone });
|
|
|
|
// 调用查看详情功能
|
|
if (typeof viewCustomerDetails === 'function') {
|
|
viewCustomerDetails(customerId, phone);
|
|
}
|
|
}
|
|
}
|
|
// 修改 updateAllCustomersPaginationInfo 函数
|
|
function updateAllCustomersPaginationInfo() {
|
|
// 定义允许显示的客户等级
|
|
const allowedLevels = ['important', 'normal', 'low-value', 'logistics', 'unclassified'];
|
|
|
|
// 修复:使用 allCustomersData 而不是 allValidCustomers
|
|
const totalPages = Math.ceil(allCustomersData.length / allCustomersPageSize);
|
|
const pageInfoElement = document.getElementById('all-customers-page-info');
|
|
const totalInfoElement = document.getElementById('all-customers-total-info');
|
|
|
|
// 修复:确保当前页码不超出范围
|
|
if (allCustomersCurrentPage > totalPages && totalPages > 0) {
|
|
allCustomersCurrentPage = totalPages;
|
|
}
|
|
|
|
if (pageInfoElement) {
|
|
pageInfoElement.textContent = `第 ${allCustomersCurrentPage} 页,共 ${totalPages} 页`;
|
|
}
|
|
|
|
if (totalInfoElement) {
|
|
const startIndex = (allCustomersCurrentPage - 1) * allCustomersPageSize + 1;
|
|
const endIndex = Math.min(startIndex + allCustomersPageSize - 1, allCustomersData.length);
|
|
totalInfoElement.textContent = `显示 ${startIndex}-${endIndex} 条 / 总计: ${allCustomersData.length} 条`;
|
|
}
|
|
|
|
// 更新分页按钮状态
|
|
const prevButton = document.getElementById('all-customers-prev-page');
|
|
const nextButton = document.getElementById('all-customers-next-page');
|
|
|
|
if (prevButton) {
|
|
prevButton.disabled = allCustomersCurrentPage === 1;
|
|
}
|
|
|
|
if (nextButton) {
|
|
nextButton.disabled = allCustomersCurrentPage === totalPages || totalPages === 0;
|
|
}
|
|
|
|
// 修复:添加调试信息
|
|
console.log('全部客户分页信息:', {
|
|
'总数据量': allCustomersData.length,
|
|
'总页数': totalPages,
|
|
'当前页': allCustomersCurrentPage,
|
|
'每页大小': allCustomersPageSize
|
|
});
|
|
}
|
|
// 添加调试日志,检查客户等级过滤
|
|
function debugCustomerLevels(customers) {
|
|
const levelCounts = {};
|
|
customers.forEach(customer => {
|
|
const level = customer.level || 'unknown';
|
|
levelCounts[level] = (levelCounts[level] || 0) + 1;
|
|
});
|
|
console.log('客户等级分布:', levelCounts);
|
|
|
|
const allowedLevels = ['important', 'normal', 'low-value', 'logistics', 'unclassified'];
|
|
const filteredCustomers = customers.filter(customer =>
|
|
allowedLevels.includes(customer.level)
|
|
);
|
|
console.log('过滤后的客户数量:', filteredCustomers.length);
|
|
console.log('允许的等级:', allowedLevels);
|
|
}
|
|
// 优化的渲染客户列表函数,使用批量渲染和高效事件委托
|
|
function renderToLevelTable(level, customers) {
|
|
const targetTbody = document.getElementById(`${level}-customers`);
|
|
if (!targetTbody) return;
|
|
|
|
// 清空表格
|
|
targetTbody.innerHTML = '';
|
|
|
|
if (!customers || customers.length === 0) {
|
|
targetTbody.innerHTML = `<tr><td colspan="9" style="text-align: center; color: #666;">暂无${getLevelName(level)}数据</td></tr>`;
|
|
return;
|
|
}
|
|
|
|
// 使用 DocumentFragment 批量创建 DOM 元素
|
|
const fragment = document.createDocumentFragment();
|
|
|
|
// 批量生成行HTML
|
|
let htmlString = '';
|
|
customers.forEach((customer) => {
|
|
const customerId = customer.id || customer.phoneNumber || '';
|
|
const phoneNumber = customer.phoneNumber || '';
|
|
|
|
htmlString += `
|
|
<tr data-id="${customerId}" data-phone="${phoneNumber}" data-level="${level}">
|
|
<td>${customer.company || '微信客户'}</td>
|
|
<td>${customer.region || '-'}</td>
|
|
<td>${customer.demand || '-'}</td>
|
|
<td>${customer.spec || '-'}</td>
|
|
<td>${customer.nickName || '-'}</td>
|
|
<td>${phoneNumber}</td>
|
|
<td>${formatTime(customer.created_at) || '-'}</td>
|
|
<td>${formatTime(customer.updated_at) || '-'}</td>
|
|
<td class="action-cell"></td>
|
|
</tr>`;
|
|
});
|
|
|
|
// 使用临时div解析HTML字符串
|
|
const tempDiv = document.createElement('div');
|
|
tempDiv.innerHTML = htmlString;
|
|
|
|
// 将所有行添加到DocumentFragment
|
|
while (tempDiv.firstChild) {
|
|
if (tempDiv.firstChild.tagName === 'TR') {
|
|
fragment.appendChild(tempDiv.firstChild);
|
|
} else {
|
|
tempDiv.firstChild.remove();
|
|
}
|
|
}
|
|
|
|
// 一次性将所有行添加到表格,减少重排重绘
|
|
targetTbody.appendChild(fragment);
|
|
|
|
// 批量添加事件监听器
|
|
const rows = targetTbody.querySelectorAll('tr');
|
|
const actionCells = targetTbody.querySelectorAll('td.action-cell');
|
|
|
|
// 为每个操作单元格添加事件委托,防止冒泡
|
|
actionCells.forEach(cell => {
|
|
cell.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
});
|
|
});
|
|
|
|
// 批量更新操作按钮
|
|
requestAnimationFrame(() => {
|
|
rows.forEach((row, index) => {
|
|
const customer = customers[index];
|
|
if (customer) {
|
|
updateActionButton(row, customer);
|
|
|
|
// 添加懒加载动画效果
|
|
row.classList.add('lazy-load');
|
|
setTimeout(() => {
|
|
row.classList.add('loaded');
|
|
}, index * 15); // 错开动画时间
|
|
}
|
|
});
|
|
});
|
|
|
|
// 使用事件委托为整个表格添加行点击事件
|
|
targetTbody.addEventListener('click', (e) => {
|
|
const row = e.target.closest('tr');
|
|
if (row && !row.querySelector('td.action-cell').contains(e.target)) {
|
|
const customerId = row.dataset.id;
|
|
const phoneNumber = row.dataset.phone;
|
|
viewCustomerDetails(customerId, phoneNumber);
|
|
}
|
|
});
|
|
|
|
console.log(`${level} 表格渲染完成,共 ${customers.length} 行`);
|
|
}
|
|
|
|
// 自动刷新配置
|
|
const AUTO_REFRESH_CONFIG = {
|
|
enabled: true, // 是否启用自动刷新
|
|
interval: 15000, // 15秒刷新间隔
|
|
currentPage: null // 当前激活的页面
|
|
};
|
|
|
|
// 初始化自动刷新功能
|
|
function initAutoRefresh() {
|
|
console.log('初始化自动刷新功能');
|
|
|
|
// 监听页面切换
|
|
const navItems = document.querySelectorAll('.nav-item[data-page]');
|
|
navItems.forEach(item => {
|
|
item.addEventListener('click', function () {
|
|
const pageId = this.getAttribute('data-page');
|
|
AUTO_REFRESH_CONFIG.currentPage = pageId;
|
|
|
|
// 切换页面后立即刷新数据
|
|
setTimeout(() => {
|
|
refreshCurrentPageData();
|
|
}, 300);
|
|
});
|
|
});
|
|
|
|
// 设置定时刷新
|
|
if (AUTO_REFRESH_CONFIG.enabled) {
|
|
setInterval(() => {
|
|
if (AUTO_REFRESH_CONFIG.currentPage) {
|
|
refreshCurrentPageData();
|
|
}
|
|
}, AUTO_REFRESH_CONFIG.interval);
|
|
}
|
|
|
|
// 初始设置当前页面
|
|
const activeNavItem = document.querySelector('.nav-item.active[data-page]');
|
|
if (activeNavItem) {
|
|
AUTO_REFRESH_CONFIG.currentPage = activeNavItem.getAttribute('data-page');
|
|
}
|
|
}
|
|
|
|
function validateCustomerLevel(level) {
|
|
const validLevels = ['important', 'normal', 'low', 'logistics', 'unclassified',
|
|
'company-sea-pools', 'organization-sea-pools', 'department-sea-pools'];
|
|
return validLevels.includes(level);
|
|
}
|
|
|
|
// 刷新当前页面数据
|
|
function refreshCurrentPageData() {
|
|
if (!AUTO_REFRESH_CONFIG.currentPage) return;
|
|
|
|
console.log(`自动刷新 ${AUTO_REFRESH_CONFIG.currentPage} 页面数据`);
|
|
|
|
switch (AUTO_REFRESH_CONFIG.currentPage) {
|
|
case 'dashboard':
|
|
refreshDashboardData();
|
|
break;
|
|
case 'customers':
|
|
refreshCustomersData();
|
|
break;
|
|
case 'follow-up':
|
|
refreshFollowUpData();
|
|
break;
|
|
case 'analytics':
|
|
// 数据分析页面暂不刷新
|
|
break;
|
|
case 'schedule':
|
|
// 日程安排页面暂不刷新
|
|
break;
|
|
case 'settings':
|
|
// 系统设置页面暂不刷新
|
|
break;
|
|
}
|
|
}
|
|
// 刷新控制面板数据
|
|
// 优化刷新控制面板数据函数,确保最近客户数据正确显示和高效加载
|
|
function refreshDashboardData() {
|
|
console.log('刷新控制面板数据');
|
|
|
|
// 尝试从多种来源获取客户数据
|
|
let allCustomers = [];
|
|
|
|
// 1. 尝试直接从customerCache获取(如果存在)
|
|
if (typeof customerCache !== 'undefined' && customerCache) {
|
|
if (customerCache['all'] && customerCache['all'].data && Array.isArray(customerCache['all'].data)) {
|
|
allCustomers = customerCache['all'].data;
|
|
console.log('从customerCache[all].data获取数据,数量:', allCustomers.length);
|
|
} else if (typeof customerCache.get === 'function') {
|
|
const cachedData = customerCache.get('all');
|
|
if (cachedData && Array.isArray(cachedData)) {
|
|
allCustomers = cachedData;
|
|
console.log('从customerCache.get("all")获取数据,数量:', allCustomers.length);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. 尝试从全局变量获取
|
|
if (allCustomers.length === 0) {
|
|
if (window.allCustomersData && Array.isArray(window.allCustomersData)) {
|
|
allCustomers = window.allCustomersData;
|
|
console.log('从window.allCustomersData获取数据,数量:', allCustomers.length);
|
|
} else if (window.customers && Array.isArray(window.customers)) {
|
|
allCustomers = window.customers;
|
|
console.log('从window.customers获取数据,数量:', allCustomers.length);
|
|
}
|
|
}
|
|
|
|
// 3. 如果仍然没有数据,异步获取
|
|
if (allCustomers.length > 0) {
|
|
// 移除自动刷新调用,避免页面闪动
|
|
} else {
|
|
console.log('没有缓存数据,异步获取客户数据...');
|
|
// 如果没有缓存数据,立即刷新
|
|
refreshCustomerData('all').then(data => {
|
|
console.log('异步获取到客户数据,数量:', data?.length || 0);
|
|
// 移除自动刷新调用,避免页面闪动
|
|
}).catch(error => {
|
|
console.error('获取客户数据失败:', error);
|
|
// 移除自动刷新调用,避免页面闪动
|
|
});
|
|
}
|
|
|
|
updateDashboardStats();
|
|
}
|
|
|
|
// 刷新客户管理页面数据 - 添加防抖和加载状态,保持时间筛选
|
|
let refreshCustomersDebounceTimer = null;
|
|
|
|
function refreshCustomersData() {
|
|
// 清除之前的定时器
|
|
if (refreshCustomersDebounceTimer) {
|
|
clearTimeout(refreshCustomersDebounceTimer);
|
|
}
|
|
|
|
// 设置防抖定时器,避免频繁切换标签时重复加载
|
|
refreshCustomersDebounceTimer = setTimeout(() => {
|
|
console.log('刷新客户管理页面数据');
|
|
|
|
// 显示加载状态
|
|
const loadingIndicator = document.getElementById('customers-loading-indicator');
|
|
if (loadingIndicator) {
|
|
loadingIndicator.style.display = 'block';
|
|
}
|
|
|
|
// 获取当前激活的客户等级标签
|
|
const activeLevelTab = document.querySelector('.level-tab.active');
|
|
if (activeLevelTab) {
|
|
const level = activeLevelTab.getAttribute('data-level');
|
|
const levelMap = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'low': 'low-value',
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified',
|
|
'public-sea': 'public-sea',
|
|
'all': 'all'
|
|
};
|
|
|
|
const backendLevel = levelMap[level] || level;
|
|
|
|
// 检查是否有时间筛选,如果有则使用带时间筛选的刷新
|
|
if (window.currentTimeFilter) {
|
|
console.log('检测到时间筛选,使用带筛选的刷新');
|
|
refreshCustomerDataWithTimeFilter(backendLevel);
|
|
} else {
|
|
console.log('无时间筛选,使用普通刷新');
|
|
// 执行数据刷新,使用Promise链式调用确保加载完成后隐藏加载状态
|
|
Promise.resolve().then(() => {
|
|
return refreshCustomerData(backendLevel);
|
|
}).finally(() => {
|
|
// 隐藏加载状态
|
|
if (loadingIndicator) {
|
|
loadingIndicator.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}, 300); // 300ms防抖延迟
|
|
}
|
|
|
|
// 刷新跟进页面数据
|
|
function refreshFollowUpData() {
|
|
console.log('刷新跟进页面数据');
|
|
// 这里可以添加跟进数据的刷新逻辑
|
|
// 例如:fetchFollowUpRecords();
|
|
}
|
|
|
|
// 更新控制面板统计卡片
|
|
function updateDashboardStats() {
|
|
// 这里可以添加统计数据的更新逻辑
|
|
// 例如从API获取最新的统计数据
|
|
console.log('更新控制面板统计卡片');
|
|
}
|
|
|
|
// 获取等级显示名称
|
|
function getLevelName(level) {
|
|
const levelNames = {
|
|
'important': '重要客户',
|
|
'normal': '普通客户',
|
|
'low': '低价值客户',
|
|
'logistics': '物流客户',
|
|
'unclassified': '未分级客户',
|
|
'company-sea-pools': '公司公海池',
|
|
'organization-sea-pools': '组织公海池',
|
|
'department-sea-pools': '部门公海池',
|
|
'all': '全部客户'
|
|
};
|
|
return levelNames[level] || level;
|
|
}
|
|
// 辅助函数:格式化时间
|
|
// 高性能时间格式化函数 - 带简单缓存优化
|
|
const timeFormatCache = new Map(); // 简单的时间格式化缓存
|
|
|
|
// 参数说明:timeStr - 时间字符串或时间戳;isFromUpdate - 是否为用户修改后生成的时间(默认为false)
|
|
function formatTime(timeStr, isFromUpdate = false) {
|
|
if (!timeStr) return '';
|
|
|
|
// 检查缓存,包含isFromUpdate参数以区分不同情况
|
|
const cacheKey = `${String(timeStr)}_${isFromUpdate ? 'updated' : 'initial'}`;
|
|
if (timeFormatCache.has(cacheKey)) {
|
|
return timeFormatCache.get(cacheKey);
|
|
}
|
|
|
|
// 快速解析日期
|
|
let date;
|
|
if (typeof timeStr === 'string') {
|
|
// 简单处理常见的时间格式
|
|
if (timeStr.includes(' ') && !timeStr.includes('Z')) {
|
|
// LocalDateTime格式,转换为标准格式
|
|
timeStr = timeStr.replace(' ', 'T');
|
|
}
|
|
date = new Date(timeStr);
|
|
|
|
// 如果解析失败,尝试时间戳
|
|
if (isNaN(date.getTime())) {
|
|
const timestamp = parseInt(timeStr);
|
|
if (!isNaN(timestamp)) {
|
|
date = new Date(timestamp);
|
|
} else {
|
|
// 无效日期
|
|
timeFormatCache.set(cacheKey, ''); // 缓存无效结果
|
|
return '';
|
|
}
|
|
}
|
|
} else if (typeof timeStr === 'number') {
|
|
// 数字直接作为时间戳
|
|
date = new Date(timeStr);
|
|
} else {
|
|
// 无效类型
|
|
return '';
|
|
}
|
|
|
|
// 时间处理逻辑:
|
|
// 1. 初始数据(从数据库加载的创建时间和修改时间)需要加8小时(UTC转北京时间)
|
|
// 2. 用户修改后生成的修改时间需要减8小时(因为now()已是本地时间,后端会再加8小时)
|
|
let processedDate;
|
|
if (isFromUpdate) {
|
|
// 用户修改后生成的时间:减8小时
|
|
processedDate = new Date(date.getTime() - (8 * 60 * 60 * 1000));
|
|
} else {
|
|
// 初始数据(包括创建时间和修改时间):加8小时
|
|
processedDate = new Date(date.getTime() + (8 * 60 * 60 * 1000));
|
|
}
|
|
|
|
// 高效格式化 - 避免使用toLocaleString
|
|
const year = processedDate.getFullYear();
|
|
const month = String(processedDate.getMonth() + 1).padStart(2, '0');
|
|
const day = String(processedDate.getDate()).padStart(2, '0');
|
|
const hours = String(processedDate.getHours()).padStart(2, '0');
|
|
const minutes = String(processedDate.getMinutes()).padStart(2, '0');
|
|
const seconds = String(processedDate.getSeconds()).padStart(2, '0');
|
|
|
|
const formatted = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
|
|
// 缓存结果(限制缓存大小,避免内存泄漏)
|
|
if (timeFormatCache.size > 1000) {
|
|
const firstKey = timeFormatCache.keys().next().value;
|
|
timeFormatCache.delete(firstKey);
|
|
}
|
|
timeFormatCache.set(cacheKey, formatted);
|
|
|
|
return formatted;
|
|
}
|
|
|
|
// 辅助函数:将Date对象转换为LocalDateTime兼容格式(ISO格式)
|
|
// 参数说明:date - 日期对象;isFromUpdate - 是否为用户修改后生成的时间(默认为false)
|
|
function formatToLocalDateTime(date, isFromUpdate = false) {
|
|
// 时间处理逻辑:
|
|
// 1. 初始数据需要加8小时
|
|
// 2. 用户修改后生成的修改时间需要减8小时
|
|
let processedDate;
|
|
if (isFromUpdate) {
|
|
// 用户修改后生成的时间:减8小时
|
|
processedDate = new Date(date.getTime() - (8 * 60 * 60 * 1000));
|
|
} else {
|
|
// 初始数据:加8小时
|
|
processedDate = new Date(date.getTime() + (8 * 60 * 60 * 1000));
|
|
}
|
|
|
|
// 转换为ISO格式:YYYY-MM-DDTHH:mm:ss
|
|
const year = processedDate.getFullYear();
|
|
const month = String(processedDate.getMonth() + 1).padStart(2, '0');
|
|
const day = String(processedDate.getDate()).padStart(2, '0');
|
|
const hours = String(processedDate.getHours()).padStart(2, '0');
|
|
const minutes = String(processedDate.getMinutes()).padStart(2, '0');
|
|
const seconds = String(processedDate.getSeconds()).padStart(2, '0');
|
|
|
|
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
}
|
|
// 客户数据 - 添加一些测试数据
|
|
const customerData = {
|
|
'CUST-10000001': {
|
|
id: 'CUST-10000001',
|
|
company: '测试科技有限公司',
|
|
region: '上海',
|
|
level: 'important',
|
|
type: '客户端',
|
|
demand: '粉蛋,粉三',
|
|
spec: '常规规格',
|
|
nickName: '张三',
|
|
phoneNumber: '13800138001',
|
|
wechat: 'zhangsan',
|
|
account: '张三',
|
|
accountNumber: '6222021234567890123',
|
|
bank: '中国工商银行',
|
|
address: '上海市浦东新区张江高科技园区',
|
|
manager: 'user',
|
|
department: '无',
|
|
assistant: '无',
|
|
customDetails: []
|
|
},
|
|
'CUST-10000002': {
|
|
id: 'CUST-10000002',
|
|
company: '公海池测试公司',
|
|
region: '北京',//---
|
|
level: 'public-sea',
|
|
type: '供应端',//---
|
|
demand: '红蛋',
|
|
spec: '红三',
|
|
nickName: '李四',
|
|
phoneNumber: '13900139002',//---
|
|
wechat: 'lisi',
|
|
account: '李四',
|
|
accountNumber: '6222021234567890124',
|
|
bank: '中国建设银行',
|
|
address: '北京市朝阳区建国路',
|
|
manager: 'user',
|
|
department: '无',
|
|
assistant: '无',
|
|
customDetails: [],
|
|
// 公海需求字段
|
|
productName: '红蛋',//---
|
|
specification: '红三',//---
|
|
quantity: '1000件',//---
|
|
grossWeight: '25吨'//---
|
|
}
|
|
};
|
|
|
|
// 跟进记录数据
|
|
const followUpData = {};
|
|
|
|
function updateStatsCards() {
|
|
const totalCustomers = Object.keys(customerData).length;
|
|
document.querySelector('.stats-cards .stat-card:nth-child(1) .value').textContent = totalCustomers;
|
|
|
|
// 计算活跃客户数(这里假设所有客户都是活跃的)
|
|
document.querySelector('.stats-cards .stat-card:nth-child(2) .value').textContent = totalCustomers;
|
|
|
|
// 客户留存率(这里假设100%)
|
|
document.querySelector('.stats-cards .stat-card:nth-child(3) .value').textContent = totalCustomers > 0 ? '100%' : '0%';
|
|
}
|
|
// 修复后的跟进界面函数 - 显示跟进弹窗
|
|
function showFollowUpInterface(customerId, phoneNumber) {
|
|
console.log('跟进功能请求:', { customerId, phoneNumber });
|
|
|
|
// 显示跟进详情弹窗
|
|
document.getElementById('followUpDetailModal').classList.add('active');
|
|
|
|
// 保存当前客户信息
|
|
window.currentCustomerId = customerId;
|
|
window.currentCustomerPhone = phoneNumber;
|
|
|
|
// 从后端获取跟进信息
|
|
fetchFollowUpInfo(phoneNumber);
|
|
}
|
|
|
|
// 从后端获取跟进信息
|
|
function fetchFollowUpInfo(phoneNumber) {
|
|
console.log('获取跟进信息:', phoneNumber);
|
|
|
|
// 发送请求获取跟进信息,不再需要指定数据源
|
|
fetch(appendAuthParams(`/DL/api/followup/get?phoneNumber=${encodeURIComponent(phoneNumber)}`))
|
|
.then(response => response.text())
|
|
.then(followup => {
|
|
console.log('获取到的跟进信息:', followup);
|
|
// 设置到文本框中
|
|
document.getElementById('follow-up-content').value = followup || '';
|
|
})
|
|
.catch(error => {
|
|
console.error('获取跟进信息失败:', error);
|
|
// 如果获取失败,显示空文本框
|
|
document.getElementById('follow-up-content').value = '';
|
|
});
|
|
}
|
|
|
|
// 保存跟进信息
|
|
function saveFollowUpInfo() {
|
|
const phoneNumber = window.currentCustomerPhone;
|
|
if (!phoneNumber) {
|
|
alert('请先选择客户');
|
|
return;
|
|
}
|
|
|
|
const followup = document.getElementById('follow-up-content').value;
|
|
|
|
// 发送请求保存跟进信息,不再需要指定数据源
|
|
fetch(appendAuthParams(`/DL/api/followup/save?phoneNumber=${encodeURIComponent(phoneNumber)}&followup=${encodeURIComponent(followup)}`), {
|
|
method: 'POST'
|
|
})
|
|
.then(response => response.text())
|
|
.then(result => {
|
|
console.log('保存跟进信息结果:', result);
|
|
// 显示保存结果
|
|
if (result.includes('成功')) {
|
|
alert('跟进信息保存成功');
|
|
// 保存成功后关闭弹窗
|
|
const followUpDetailModal = document.getElementById('followUpDetailModal');
|
|
followUpDetailModal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
} else {
|
|
alert('跟进信息保存失败: ' + result);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('保存跟进信息失败:', error);
|
|
alert('保存跟进信息失败: ' + error.message);
|
|
});
|
|
}
|
|
|
|
// 绑定事件监听器
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// 绑定保存按钮的点击事件
|
|
const saveBtn = document.getElementById('saveFollowUpBtn');
|
|
if (saveBtn) {
|
|
saveBtn.addEventListener('click', saveFollowUpInfo);
|
|
}
|
|
|
|
// 绑定关闭按钮和弹窗外部点击事件
|
|
const followUpDetailModal = document.getElementById('followUpDetailModal');
|
|
const closeFollowUpDetail = document.getElementById('closeFollowUpDetail');
|
|
|
|
if (closeFollowUpDetail) {
|
|
// 关闭跟进详情弹窗
|
|
closeFollowUpDetail.addEventListener('click', function () {
|
|
followUpDetailModal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
});
|
|
}
|
|
|
|
if (followUpDetailModal) {
|
|
followUpDetailModal.addEventListener('click', function (e) {
|
|
if (e.target === followUpDetailModal) {
|
|
followUpDetailModal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// 简单的通知函数
|
|
function createSimpleNotification(message, type = 'info') {
|
|
// 检查是否已存在通知元素
|
|
const existingNotification = document.getElementById('simple-notification');
|
|
if (existingNotification) {
|
|
existingNotification.remove();
|
|
}
|
|
|
|
// 创建通知元素
|
|
const notification = document.createElement('div');
|
|
notification.id = 'simple-notification';
|
|
notification.textContent = message;
|
|
|
|
// 设置样式
|
|
notification.style.position = 'fixed';
|
|
notification.style.top = '20px';
|
|
notification.style.right = '20px';
|
|
notification.style.padding = '12px 20px';
|
|
notification.style.backgroundColor = type === 'success' ? '#52c41a' :
|
|
type === 'error' ? '#ff4d4f' : '#1890ff';
|
|
notification.style.color = 'white';
|
|
notification.style.borderRadius = '4px';
|
|
notification.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
|
notification.style.zIndex = '9999';
|
|
notification.style.fontSize = '14px';
|
|
notification.style.transition = 'opacity 0.3s ease';
|
|
|
|
// 添加到页面
|
|
document.body.appendChild(notification);
|
|
|
|
// 3秒后自动消失
|
|
setTimeout(() => {
|
|
notification.style.opacity = '0';
|
|
setTimeout(() => {
|
|
if (document.body.contains(notification)) {
|
|
document.body.removeChild(notification);
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// 重置跟进记录状态
|
|
function resetFollowUpState() {
|
|
// 重置跟进记录相关的状态变量
|
|
window.currentFollowUpRecords = [];
|
|
window.currentFollowUpPage = 1;
|
|
window.followUpLoading = false;
|
|
}
|
|
|
|
// 加载跟进记录
|
|
async function loadFollowUpRecords(customerId, phoneNumber) {
|
|
console.log('加载跟进记录:', { customerId, phoneNumber });
|
|
|
|
try {
|
|
// 尝试从API获取跟进记录
|
|
const url = appendAuthParams(`${API_BASE_URL}/follow-up/records`);
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({
|
|
customerId: customerId,
|
|
phoneNumber: phoneNumber,
|
|
page: 1,
|
|
pageSize: 20
|
|
})
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP错误: ${response.status}`);
|
|
const result = await response.json();
|
|
|
|
if (result.success && result.data && Array.isArray(result.data.records)) {
|
|
window.currentFollowUpRecords = result.data.records;
|
|
fillFollowUpRecords();
|
|
} else {
|
|
// 如果没有跟进记录,显示空状态
|
|
fillFollowUpRecords([]);
|
|
}
|
|
} catch (error) {
|
|
console.error('加载跟进记录失败:', error);
|
|
// 即使失败也显示空状态,避免界面空白
|
|
fillFollowUpRecords([]);
|
|
}
|
|
}
|
|
|
|
// 修复后的updateRecentCustomers函数,使用缓存数据
|
|
function updateRecentCustomers() {
|
|
const recentTable = document.getElementById('recent-customers-table');
|
|
const recentTbody = recentTable ? recentTable.querySelector('tbody') : null;
|
|
|
|
if (!recentTbody) {
|
|
console.error('最近客户表格的tbody元素未找到');
|
|
return;
|
|
}
|
|
|
|
console.log('更新最近客户显示,检查DOM元素:', { recentTable: !!recentTable, recentTbody: !!recentTbody });
|
|
|
|
recentTbody.innerHTML = ''; // 清空现有内容
|
|
|
|
// 从缓存获取所有客户数据
|
|
let allCustomers = [];
|
|
|
|
// 尝试多种数据源
|
|
if (window.allCustomersData && Array.isArray(window.allCustomersData) && window.allCustomersData.length > 0) {
|
|
console.log('使用allCustomersData,数量:', window.allCustomersData.length);
|
|
allCustomers = window.allCustomersData;
|
|
}
|
|
// 如果缓存存在且有数据
|
|
else if (window.customerCache && window.customerCache.get) {
|
|
const cachedData = window.customerCache.get('all') || [];
|
|
if (cachedData.length > 0) {
|
|
console.log('从缓存获取客户数据,数量:', cachedData.length);
|
|
allCustomers = cachedData;
|
|
}
|
|
}
|
|
// 最后尝试其他可能的数据源
|
|
else if (window.customers && Array.isArray(window.customers)) {
|
|
console.log('使用window.customers,数量:', window.customers.length);
|
|
allCustomers = window.customers;
|
|
}
|
|
else if (typeof customerData === 'object' && customerData !== null) {
|
|
console.log('使用customerData,数量:', Object.keys(customerData).length);
|
|
allCustomers = Object.values(customerData);
|
|
}
|
|
|
|
console.log('更新最近客户,可用数据数量:', allCustomers.length);
|
|
|
|
// 按时间排序(最新在前),取前5条
|
|
const sortedCustomers = allCustomers
|
|
.sort((a, b) => {
|
|
const dateA = a.updated_at ? new Date(a.updated_at) : (a.created_at ? new Date(a.created_at) : new Date(0));
|
|
const dateB = b.updated_at ? new Date(b.updated_at) : (b.created_at ? new Date(b.created_at) : new Date(0));
|
|
// 转换为北京时间进行比较
|
|
const beijingDateA = new Date(dateA.getTime() + (8 * 60 * 60 * 1000));
|
|
const beijingDateB = new Date(dateB.getTime() + (8 * 60 * 60 * 1000));
|
|
return beijingDateB - beijingDateA; // 按北京时间降序排序
|
|
})
|
|
.slice(0, 5);
|
|
|
|
if (sortedCustomers.length === 0) {
|
|
console.log('最近客户没有数据,显示空状态');
|
|
recentTbody.innerHTML = '<tr><td colspan="6" style="text-align: center; color: #666;">暂无客户数据</td></tr>';
|
|
return;
|
|
}
|
|
|
|
// 显示找到的数据
|
|
console.log('找到最近客户数据,数量:', sortedCustomers.length);
|
|
|
|
// 生成表格行 - 修复渲染逻辑,确保正确显示数据
|
|
let rowHTML = '';
|
|
sortedCustomers.forEach((customer, index) => {
|
|
// 确保客户数据有效
|
|
if (!customer) return;
|
|
|
|
// 增强字段获取,支持多种可能的字段名
|
|
const customerId = customer.id || customer.userId || '';
|
|
const phone = customer.phoneNumber || customer.phone || '';
|
|
const company = customer.company || customer.name || '微信客户';
|
|
const nickName = customer.nickName || customer.userName || '-';
|
|
const wechat = customer.wechat || '-';
|
|
|
|
console.log(`渲染最近客户 #${index + 1}:`, company, phone, customerId);
|
|
|
|
// 使用模板字符串构建表格行
|
|
rowHTML += `
|
|
<tr data-id="${customerId || phone}" data-phone="${phone}" class="customer-row recent-customer">
|
|
<td>${company}</td>
|
|
<td style="display: flex; flex-direction: column;">
|
|
<div>${nickName}</div>
|
|
<div style="font-size: 12px; color: #666;">微信: ${wechat}</div>
|
|
</td>
|
|
<td>${phone}</td>
|
|
<td class="last-order-cell"></td> <!-- 最后订单(暂未实现) -->
|
|
<td class="status-cell">
|
|
<div class="status active">活跃</div>
|
|
</td>
|
|
<td>
|
|
<button class="action-btn follow-up-btn" data-id="${customerId}" data-phone="${phone}">跟进</button>
|
|
<button class="action-btn view-details" data-id="${customerId}" data-phone="${phone}">查看详情</button>
|
|
</td>
|
|
</tr>`;
|
|
});
|
|
|
|
// 一次性设置所有行,提高性能
|
|
console.log('设置最近客户表格内容,生成行数:', sortedCustomers.length);
|
|
recentTbody.innerHTML = rowHTML;
|
|
|
|
// 绑定事件 - 使用委托方式更高效
|
|
recentTable.addEventListener('click', (e) => {
|
|
const followBtn = e.target.closest('.follow-up-btn');
|
|
const detailBtn = e.target.closest('.view-details');
|
|
const row = e.target.closest('tr.customer-row');
|
|
|
|
if (followBtn) {
|
|
e.stopPropagation();
|
|
const customerId = followBtn.getAttribute('data-id');
|
|
const phone = followBtn.getAttribute('data-phone');
|
|
console.log('跟进按钮被点击:', { customerId, phone });
|
|
// 调用跟进功能
|
|
if (typeof showFollowUpInterface === 'function') {
|
|
showFollowUpInterface(customerId, phone);
|
|
} else {
|
|
// 如果跟进函数未定义,显示提示
|
|
createSimpleNotification('跟进功能正在实现中');
|
|
}
|
|
}
|
|
else if (detailBtn) {
|
|
e.stopPropagation();
|
|
const customerId = detailBtn.getAttribute('data-id');
|
|
const phone = detailBtn.getAttribute('data-phone');
|
|
console.log('查看详情按钮被点击:', { customerId, phone });
|
|
// 调用查看详情功能
|
|
if (typeof viewCustomerDetails === 'function') {
|
|
viewCustomerDetails(customerId, phone);
|
|
}
|
|
}
|
|
else if (row) {
|
|
const customerId = row.getAttribute('data-id');
|
|
const phone = row.getAttribute('data-phone');
|
|
console.log('客户行被点击:', { customerId, phone });
|
|
// 调用查看详情功能
|
|
if (typeof viewCustomerDetails === 'function') {
|
|
viewCustomerDetails(customerId, phone);
|
|
}
|
|
}
|
|
});
|
|
|
|
// 确保表格可见
|
|
recentTable.style.display = 'table';
|
|
console.log('最近客户表格渲染完成');
|
|
}
|
|
|
|
/**
|
|
* 更新「客户管理」对应等级的表格
|
|
* @param {Object} customer - 新增的客户数据
|
|
*/
|
|
function updateCustomerTable(customer) {
|
|
const level = customer.level;
|
|
let targetTbody = document.getElementById(`${level}-customers`);
|
|
|
|
// 如果等级表格不存在(如"物流客户"),动态创建
|
|
if (!targetTbody) {
|
|
targetTbody = document.createElement('tbody');
|
|
targetTbody.id = `${level}-customers`;
|
|
targetTbody.style.display = 'none'; // 默认隐藏(切换等级时显示)
|
|
document.getElementById('customers-table').appendChild(targetTbody);
|
|
}
|
|
|
|
// 检查是否有"暂无数据"行,如果有则移除
|
|
const noDataRow = targetTbody.querySelector('tr td[colspan="7"]');
|
|
if (noDataRow) {
|
|
targetTbody.innerHTML = '';
|
|
}
|
|
|
|
// 生成客户行
|
|
const row = document.createElement('tr');
|
|
row.dataset.id = customer.id || customer.phoneNumber;
|
|
row.innerHTML = `
|
|
<td>${customer.company}</td>
|
|
<td>${customer.region}</td>
|
|
<td>${customer.demand}</td>
|
|
<td>${customer.spec}</td>
|
|
<td>${customer.nickName}</td>
|
|
<td>${customer.phoneNumber}</td>
|
|
<td><button class="action-btn view-details" data-id="${customer.id}">查看详情</button></td>
|
|
`;
|
|
// 绑定「查看详情」按钮事件
|
|
const viewButton = row.querySelector('.view-details');
|
|
viewButton.addEventListener('click', (e) => {
|
|
e.stopPropagation(); // 阻止冒泡,避免触发行的点击事件
|
|
viewCustomerDetails(customer.id, customer.phoneNumber);
|
|
});
|
|
|
|
// 绑定行点击事件,实现点击任意位置查看详情
|
|
row.addEventListener('click', () => {
|
|
const companyId = customer.id && !isPhoneNumberFormat(customer.id) ? customer.id : '';
|
|
viewCustomerDetails(companyId, customer.phoneNumber);
|
|
});
|
|
|
|
targetTbody.appendChild(row);
|
|
|
|
// 如果当前选中的等级是新增客户的等级,刷新分页
|
|
if (currentLevel === level) {
|
|
safeUpdatePagination();
|
|
}
|
|
}
|
|
async function fetchPublicSeaData() {
|
|
try {
|
|
const response = await fetch(`${API_BASE_URL}/pool/customers`, {
|
|
method: 'GET',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded'
|
|
}
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`请求失败: ${response.status}`);
|
|
const result = await response.json();
|
|
|
|
if (!result.success) {
|
|
throw new Error(result.message || '获取公海池数据失败');
|
|
}
|
|
|
|
// 后端返回的是 Map<String, UnifiedCustomerDTO>
|
|
// 需要将 Map 转换为数组
|
|
const dataMap = result.data || {};
|
|
const customerArray = Object.values(dataMap); // 转换为数组
|
|
|
|
console.log('接收到的客户数据:', customerArray); // 调试日志
|
|
// 处理公海池客户数据,确保等级正确
|
|
const publicSeaCustomers = customerArray.map(customer => ({
|
|
...customer,
|
|
level: 'public-sea' // 强制设置为公海池等级
|
|
}));
|
|
// 渲染到公海池表格
|
|
renderToLevelTable('public-sea', publicSeaCustomers);
|
|
|
|
// 刷新分页(如果当前查看的是公海池)
|
|
if (currentLevel === 'public-sea') {
|
|
safeUpdatePagination();
|
|
}
|
|
} catch (error) {
|
|
console.error('公海池数据加载失败:', error);
|
|
alert('加载客户数据失败: ' + error.message);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 当前查看的客户ID
|
|
let currentCustomerId = null;
|
|
|
|
// 当前查看的跟进记录ID
|
|
let currentFollowUpId = null;
|
|
|
|
// 表单提交事件
|
|
document.getElementById('addCustomerForm').addEventListener('submit', (e) => {
|
|
e.preventDefault(); // 阻止默认刷新行为
|
|
|
|
// 获取表单字段(需与弹窗中的ID对应)
|
|
const addCompany = document.getElementById('add-company');
|
|
const addContact = document.getElementById('add-contact');
|
|
const addPhone = document.getElementById('add-phone');
|
|
const addLevel = document.getElementById('customer-levels');
|
|
|
|
// 验证必填项
|
|
if (!addCompany.value.trim() || !addContact.value.trim() || !addPhone.value.trim() || !addLevel.value) {
|
|
alert('客户公司、联系人、电话和客户等级为必填项!');
|
|
return;
|
|
}
|
|
|
|
// 收集表单数据(与客户详情的字段一致)
|
|
const newCustomer = {
|
|
id: document.getElementById('add-id').value, // 使用生成的ID
|
|
company: addCompany.value.trim(),
|
|
region: document.getElementById('add-region').value.trim(),
|
|
level: addLevel.value, // 客户等级(下拉框值)
|
|
type: document.getElementById('add-type').value.trim(),
|
|
demand: document.getElementById('add-needs').value.trim(), // 已经是中文文本了
|
|
spec: document.getElementById('add-spec').value.trim(),// 产品规格
|
|
nickName: addContact.value.trim(),// 联系人
|
|
phoneNumber: addPhone.value.trim(),
|
|
wechat: document.getElementById('add-wechat').value.trim(),
|
|
account: document.getElementById('add-account').value.trim(),
|
|
accountNumber: document.getElementById('add-account-number').value.trim(),
|
|
bank: document.getElementById('add-bank').value.trim(),
|
|
address: document.getElementById('add-address').value.trim(),
|
|
// 负责人信息(固定默认值)
|
|
manager: document.getElementById('add-manager').value,
|
|
department: document.getElementById('add-department').value,
|
|
assistant: document.getElementById('add-assistant').value,
|
|
customDetails: [] // 初始无附加联系人
|
|
};
|
|
|
|
// 存入客户数据(内存暂存,后续可对接后端)
|
|
customerData[newCustomer.id] = newCustomer;
|
|
|
|
// 更新表格(最近客户 + 对应等级的客户管理表)
|
|
updateRecentCustomers();
|
|
updateCustomerTable(newCustomer);
|
|
updateStatsCards();
|
|
|
|
// 关闭弹窗并提示
|
|
document.getElementById('addCustomerModal').classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
alert('客户新增成功!');
|
|
});
|
|
|
|
|
|
// 移除未定义变量的console日志
|
|
// console.log('查看客户详情参数:', { customerId, phoneNumber, isCompanyId });
|
|
// 优化的客户详情查看功能
|
|
async function viewCustomerDetails(customerId, phoneNumber, targetProductItemId = null, fromNotification = false) {
|
|
// 重置客户数据状态
|
|
resetCustomerDataState();
|
|
|
|
// 参数检查
|
|
if ((!customerId || String(customerId).trim() === '') && (!phoneNumber || String(phoneNumber).trim() === '')) {
|
|
// 使用更友好的提示方式
|
|
showNotification('客户ID和电话号码不能都为空', 'error');
|
|
return;
|
|
}
|
|
|
|
currentCustomerId = customerId || phoneNumber;
|
|
resetEditStateToInitial();
|
|
|
|
try {
|
|
// 尝试按优先级查询客户信息:API查询 -> 本地缓存
|
|
const customerData = await fetchCustomerData(customerId, phoneNumber, targetProductItemId);
|
|
|
|
if (!customerData.customer) {
|
|
showNotification('未找到客户信息,请检查客户是否存在或数据是否加载完成', 'error');
|
|
return;
|
|
}
|
|
|
|
// 如果是从通知弹窗点击进入,更新notice状态为old
|
|
if (fromNotification) {
|
|
console.log('📋 从通知弹窗查看客户详情,更新notice状态为old');
|
|
// 发送请求更新notice状态
|
|
fetch(`/DL/supply/pool/customers/${customerId}/notice`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ notice: 'old' })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
console.log('✅ 更新客户通知状态成功,响应:', data);
|
|
// 从通知列表中移除对应的通知项
|
|
const notificationItems = document.querySelectorAll('.notification-item');
|
|
notificationItems.forEach(item => {
|
|
const customerIdElement = item.querySelector('.customer-id');
|
|
if (customerIdElement && customerIdElement.textContent.includes(customerId)) {
|
|
item.remove();
|
|
}
|
|
});
|
|
// 检查是否还有通知项
|
|
const remainingItems = document.querySelectorAll('.notification-item');
|
|
if (remainingItems.length === 0) {
|
|
const notificationContent = document.getElementById('notificationContent');
|
|
notificationContent.innerHTML = '<div class="notification-empty"><p>暂无通知</p></div>';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('❌ 更新客户通知状态失败:', error);
|
|
});
|
|
}
|
|
|
|
// 处理客户数据
|
|
const { customer, dataSource, apiUsed } = customerData;
|
|
|
|
// 标准化客户等级
|
|
const normalizedLevel = standardizeCustomerLevel(customer.level) || 'unclassified';
|
|
|
|
// 处理客户数据,确保字段完整
|
|
const processedCustomer = ensureCustomerFields(
|
|
{ ...customer, level: normalizedLevel },
|
|
customerId || phoneNumber
|
|
);
|
|
|
|
// 填充详情弹窗数据
|
|
fillCustomerDetails(processedCustomer);
|
|
|
|
// 存储当前客户数据
|
|
window.currentCustomerData = {
|
|
...processedCustomer,
|
|
dataSource,
|
|
apiUsed,
|
|
_currentSelectedDemandId: targetProductItemId || customer.targetProductItemId
|
|
};
|
|
|
|
// 显示客户详情弹窗
|
|
showCustomerModal();
|
|
|
|
} catch (error) {
|
|
// 静默记录错误,不阻塞UI
|
|
console.error('获取客户详情失败:', error);
|
|
showNotification('获取客户详情失败', 'error');
|
|
}
|
|
}
|
|
|
|
// 优化的客户数据获取函数
|
|
async function fetchCustomerData(customerId, phoneNumber, targetProductItemId) {
|
|
let customer = null;
|
|
let dataSource = 'unknown';
|
|
let apiUsed = '';
|
|
|
|
// 判断是否为公司ID
|
|
const isCompanyId = customerId && customerId.trim() !== '' && !isPhoneNumberFormat(customerId);
|
|
|
|
// 1. 首先尝试使用公司ID查询
|
|
if (isCompanyId) {
|
|
try {
|
|
let url = appendAuthParams(`${API_BASE_URL}/pool/customers/${encodeURIComponent(customerId)}`);
|
|
if (targetProductItemId) {
|
|
url += `&targetProductItemId=${encodeURIComponent(targetProductItemId)}`;
|
|
}
|
|
|
|
const response = await fetch(url);
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
if (result.success && result.data) {
|
|
return {
|
|
customer: result.data,
|
|
dataSource: 'company',
|
|
apiUsed: 'company-api'
|
|
};
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// 静默失败,继续尝试其他查询方式
|
|
}
|
|
}
|
|
|
|
// 2. 尝试使用电话号码查询
|
|
if (!customer && phoneNumber && phoneNumber.trim() !== '') {
|
|
try {
|
|
let url = appendAuthParams(`${API_BASE_URL}/pool/customers/by-phone?phoneNumber=${encodeURIComponent(phoneNumber)}`);
|
|
if (targetProductItemId) {
|
|
url += `&targetProductItemId=${encodeURIComponent(targetProductItemId)}`;
|
|
}
|
|
|
|
const response = await fetch(url);
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
if (result.success && result.data) {
|
|
return {
|
|
customer: result.data,
|
|
dataSource: 'phone',
|
|
apiUsed: 'phone-api'
|
|
};
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// 静默失败,继续尝试其他查询方式
|
|
}
|
|
}
|
|
|
|
// 3. 尝试从本地缓存查找
|
|
if (!customer && window.customerData) {
|
|
if (customerId && window.customerData[customerId]) {
|
|
return {
|
|
customer: window.customerData[customerId],
|
|
dataSource: 'local',
|
|
apiUsed: 'local-cache'
|
|
};
|
|
} else if (phoneNumber && window.customerData[phoneNumber]) {
|
|
return {
|
|
customer: window.customerData[phoneNumber],
|
|
dataSource: 'local',
|
|
apiUsed: 'local-cache'
|
|
};
|
|
}
|
|
}
|
|
|
|
return { customer, dataSource, apiUsed };
|
|
}
|
|
|
|
// 简单的通知函数,替代alert
|
|
function showNotification(message, type = 'info') {
|
|
// 创建通知元素
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification notification-${type}`;
|
|
notification.textContent = message;
|
|
|
|
// 添加到文档
|
|
document.body.appendChild(notification);
|
|
|
|
// 自动移除
|
|
setTimeout(() => {
|
|
notification.style.opacity = '0';
|
|
// 等待动画完成后移除元素
|
|
setTimeout(() => {
|
|
notification.remove();
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// 添加通知系统的CSS样式
|
|
function addNotificationStyles() {
|
|
// 检查是否已添加样式
|
|
if (document.getElementById('notification-styles')) return;
|
|
|
|
const style = document.createElement('style');
|
|
style.id = 'notification-styles';
|
|
style.textContent = `
|
|
.notification {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
padding: 12px 20px;
|
|
border-radius: 4px;
|
|
color: white;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
z-index: 9999;
|
|
transition: opacity 0.3s ease;
|
|
max-width: 300px;
|
|
word-wrap: break-word;
|
|
}
|
|
.notification-info {
|
|
background-color: #1890ff;
|
|
}
|
|
.notification-success {
|
|
background-color: #52c41a;
|
|
}
|
|
.notification-error {
|
|
background-color: #ff4d4f;
|
|
}
|
|
.notification-warning {
|
|
background-color: #faad14;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
// 添加表格行悬停效果优化
|
|
function addTablePerformanceStyles() {
|
|
if (document.getElementById('table-performance-styles')) return;
|
|
|
|
const style = document.createElement('style');
|
|
style.id = 'table-performance-styles';
|
|
style.textContent = `
|
|
/* 减少表格重绘 */
|
|
table {
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
}
|
|
|
|
/* 优化表格行悬停效果 */
|
|
tbody tr {
|
|
transition: background-color 0.1s ease;
|
|
will-change: background-color;
|
|
}
|
|
|
|
/* 减少动画效果以提高性能 */
|
|
.action-btn {
|
|
transition: all 0.1s ease;
|
|
}
|
|
|
|
/* 延迟加载非关键CSS */
|
|
.lazy-load {
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
.lazy-load.loaded {
|
|
opacity: 1;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
// 在页面加载时添加样式
|
|
window.addEventListener('DOMContentLoaded', function() {
|
|
addNotificationStyles();
|
|
addTablePerformanceStyles();
|
|
|
|
// 初始化图片延迟加载
|
|
initLazyLoading();
|
|
});
|
|
|
|
// 图片延迟加载实现
|
|
function initLazyLoading() {
|
|
// 检查浏览器是否支持IntersectionObserver
|
|
if ('IntersectionObserver' in window) {
|
|
const imageObserver = new IntersectionObserver((entries, observer) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
const img = entry.target;
|
|
const src = img.getAttribute('data-src');
|
|
|
|
if (src) {
|
|
img.src = src;
|
|
img.removeAttribute('data-src');
|
|
img.classList.add('loaded');
|
|
}
|
|
|
|
observer.unobserve(img);
|
|
}
|
|
});
|
|
});
|
|
|
|
// 获取所有需要延迟加载的图片
|
|
document.querySelectorAll('img[data-src]').forEach(img => {
|
|
imageObserver.observe(img);
|
|
});
|
|
} else {
|
|
// 回退到传统方法
|
|
fallbackLazyLoad();
|
|
}
|
|
}
|
|
|
|
// 回退到滚动监听的懒加载方法
|
|
function fallbackLazyLoad() {
|
|
const lazyImages = document.querySelectorAll('img[data-src]');
|
|
|
|
// 使用requestAnimationFrame优化滚动加载
|
|
function loadImages() {
|
|
requestAnimationFrame(() => {
|
|
lazyImages.forEach(img => {
|
|
if (isElementInViewport(img)) {
|
|
const src = img.getAttribute('data-src');
|
|
if (src) {
|
|
img.src = src;
|
|
img.removeAttribute('data-src');
|
|
img.classList.add('loaded');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function isElementInViewport(el) {
|
|
const rect = el.getBoundingClientRect();
|
|
return (
|
|
rect.top >= 0 &&
|
|
rect.left >= 0 &&
|
|
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
|
|
);
|
|
}
|
|
|
|
// 初始加载
|
|
loadImages();
|
|
|
|
// 使用节流函数优化滚动事件
|
|
const throttledLoadImages = throttle(loadImages, 200);
|
|
window.addEventListener('scroll', throttledLoadImages);
|
|
}
|
|
|
|
// 节流函数 - 限制函数调用频率
|
|
function throttle(func, delay) {
|
|
let inThrottle;
|
|
return function() {
|
|
const args = arguments;
|
|
const context = this;
|
|
if (!inThrottle) {
|
|
func.apply(context, args);
|
|
inThrottle = true;
|
|
setTimeout(() => inThrottle = false, delay);
|
|
}
|
|
};
|
|
}
|
|
|
|
// 防抖函数 - 延迟函数调用直到事件停止触发
|
|
function debounce(func, delay) {
|
|
let timeout;
|
|
return function() {
|
|
const args = arguments;
|
|
const context = this;
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => func.apply(context, args), delay);
|
|
};
|
|
}
|
|
|
|
// DOM批处理操作 - 减少重绘和回流
|
|
function batchDOMOperations(operations) {
|
|
// 使用DocumentFragment进行DOM操作批处理
|
|
const fragment = document.createDocumentFragment();
|
|
|
|
// 执行所有操作并将结果添加到fragment
|
|
operations.forEach(operation => {
|
|
if (typeof operation === 'function') {
|
|
const result = operation(fragment);
|
|
if (result && result.nodeType) {
|
|
fragment.appendChild(result);
|
|
}
|
|
}
|
|
});
|
|
|
|
return fragment;
|
|
}
|
|
|
|
// 添加等级标准化函数
|
|
function standardizeCustomerLevel(level) {
|
|
if (!level) return 'unclassified';
|
|
|
|
const levelMap = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'normal': 'normal', // 统一使用normal
|
|
'low-value': 'low-value',
|
|
'low': 'low-value', // 前端low对应后端low-value
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified',
|
|
'company-sea-pools': 'company-sea-pools',
|
|
'organization-sea-pools': 'organization-sea-pools',
|
|
'department-sea-pools': 'department-sea-pools',
|
|
'all': 'all'
|
|
};
|
|
|
|
return levelMap[level] || 'unclassified';
|
|
}
|
|
// 新增:重置客户数据状态函数
|
|
function resetCustomerDataState() {
|
|
// 重置公海需求管理器状态
|
|
if (window.publicSeaDemandManager) {
|
|
publicSeaDemandManager.reset();
|
|
}
|
|
|
|
// 清除当前客户数据缓存
|
|
window.currentCustomerData = null;
|
|
window.updatedCustomerData = null;
|
|
window.currentCartItemId = null;
|
|
|
|
console.log('客户数据状态已重置');
|
|
}
|
|
|
|
// 添加登录信息打印和获取函数
|
|
function getLoginInfo() {
|
|
// 从URL参数获取认证信息
|
|
const urlAuthInfo = getAuthInfoFromURL();
|
|
|
|
let loginInfo;
|
|
|
|
// 如果URL参数中有认证信息,使用URL参数
|
|
if (urlAuthInfo.userName && urlAuthInfo.userName !== '无' && urlAuthInfo.userName !== '') {
|
|
console.log('✅ 使用URL参数中的认证信息');
|
|
loginInfo = {
|
|
managerId: urlAuthInfo.managerId || '无',
|
|
managercompany: urlAuthInfo.managercompany || '无',
|
|
managerdepartment: urlAuthInfo.managerdepartment || '无',
|
|
organization: urlAuthInfo.organization || '无',
|
|
role: urlAuthInfo.role || '无',
|
|
userName: urlAuthInfo.userName || '无',
|
|
assistant: urlAuthInfo.assistant || '无'
|
|
};
|
|
} else {
|
|
// URL参数中没有有效信息,全部设置为'无'
|
|
console.log('❌ URL参数中没有有效的认证信息');
|
|
loginInfo = {
|
|
managerId: '无',
|
|
managercompany: '无',
|
|
managerdepartment: '无',
|
|
organization: '无',
|
|
role: '无',
|
|
userName: '无',
|
|
assistant: '无'
|
|
};
|
|
}
|
|
|
|
// 打印登录信息详情
|
|
console.log('=== 登录信息详情 ===');
|
|
console.log('当前用户信息:', loginInfo);
|
|
console.log('负责人Id:', loginInfo.managerId);
|
|
console.log('公司:', loginInfo.managercompany);
|
|
console.log('部门:', loginInfo.managerdepartment);
|
|
console.log('组织:', loginInfo.organization);
|
|
console.log('职位:', loginInfo.role);
|
|
console.log('负责人:', loginInfo.userName);
|
|
console.log('协助人:', loginInfo.assistant);
|
|
console.log('==================');
|
|
|
|
// 存储到全局变量中,方便其他函数使用
|
|
window.loginInfo = loginInfo;
|
|
return window.loginInfo;
|
|
}
|
|
// 辅助函数:判断是否为电话号码格式
|
|
function isPhoneNumberFormat(str) {
|
|
if (!str) return false;
|
|
// 简单的电话号码格式判断:11位数字
|
|
const phoneRegex = /^1\d{10}$/;
|
|
return phoneRegex.test(str.trim());
|
|
}
|
|
// 新增:确保客户对象包含所有必要字段
|
|
function ensureCustomerFields(customer, customerId) {
|
|
const ensuredCustomer = { ...customer };
|
|
|
|
// 确保有ID字段
|
|
if (!ensuredCustomer.id) {
|
|
ensuredCustomer.id = customerId;
|
|
}
|
|
|
|
// 确保有基本字段,避免显示时出现undefined
|
|
const requiredFields = {
|
|
company: '未设置公司',
|
|
region: '未设置地区',
|
|
level: 'unclassified',
|
|
type: '未设置类型',
|
|
demand: '未设置需求',
|
|
spec: '未设置规格',
|
|
nickName: '未设置联系人',
|
|
phoneNumber: customerId, // 使用传入的ID作为电话号码
|
|
wechat: '未设置微信',
|
|
account: '未设置账户',
|
|
accountNumber: '未设置账号',
|
|
bank: '未设置开户行',
|
|
address: '未设置地址',
|
|
manager: '未设置负责人',
|
|
department: '未设置部门',
|
|
assistant: '未设置协助人',
|
|
created_at: new Date(new Date().getTime() + (8 * 60 * 60 * 1000)).toISOString(),
|
|
updated_at: new Date(new Date().getTime() + (8 * 60 * 60 * 1000)).toISOString()
|
|
};
|
|
|
|
// 填充缺失的字段
|
|
Object.keys(requiredFields).forEach(field => {
|
|
if (!ensuredCustomer[field] && ensuredCustomer[field] !== '') {
|
|
ensuredCustomer[field] = requiredFields[field];
|
|
}
|
|
});
|
|
|
|
return ensuredCustomer;
|
|
}
|
|
|
|
|
|
// 修改判断客户是否属于公海池的函数
|
|
function isPublicSeaCustomer(customer) {
|
|
const publicSeaLevels = ['company-sea-pools', 'organization-sea-pools', 'department-sea-pools'];
|
|
return publicSeaLevels.includes(customer.level);
|
|
}
|
|
|
|
// 修改判断客户是否属于全部客户的函数
|
|
function isAllCustomer(customer) {
|
|
const validLevels = ['important', 'normal', 'low-value', 'logistics', 'unclassified'];
|
|
return validLevels.includes(customer.level);
|
|
}
|
|
|
|
// 更新操作按钮的显示
|
|
// 优化的updateActionButton函数 - 只生成HTML,不单独绑定事件(由事件委托处理)
|
|
function updateActionButton(row, customer) {
|
|
const actionCell = row.querySelector('td:last-child');
|
|
if (!actionCell) return;
|
|
|
|
// 定义公海池等级
|
|
const publicSeaLevels = ['company-sea-pools', 'organization-sea-pools', 'department-sea-pools'];
|
|
const isPublicSea = publicSeaLevels.includes(customer.level);
|
|
|
|
// 直接生成HTML,不进行任何事件绑定
|
|
if (isPublicSea) {
|
|
actionCell.innerHTML = `
|
|
<button class="action-btn view-details"
|
|
data-company-id="${customer.id || ''}"
|
|
data-phone="${customer.phoneNumber || ''}">
|
|
<i class="fas fa-eye"></i> 查看详情
|
|
</button>
|
|
`;
|
|
} else {
|
|
actionCell.innerHTML = `
|
|
<button class="action-btn follow-up-btn"
|
|
data-company-id="${customer.id || ''}"
|
|
data-phone="${customer.phoneNumber || ''}">
|
|
<i class="fas fa-tasks"></i> 跟进
|
|
</button>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// 优化的公司公海池表格渲染函数 - 使用文档片段和事件委托
|
|
function renderCompanySeaPoolsTable(customers) {
|
|
const targetTbody = document.getElementById('company-sea-pools-customers');
|
|
if (!targetTbody) {
|
|
console.error('公司公海池表格体未找到');
|
|
return;
|
|
}
|
|
|
|
// 清空表格
|
|
targetTbody.innerHTML = '';
|
|
|
|
if (!customers || customers.length === 0) {
|
|
targetTbody.innerHTML = `<tr><td colspan="9" style="text-align: center; color: #666;">暂无公司公海池客户数据</td></tr>`;
|
|
return;
|
|
}
|
|
|
|
// 使用文档片段批量添加DOM元素
|
|
const fragment = document.createDocumentFragment();
|
|
|
|
customers.forEach((customer) => {
|
|
const row = document.createElement('tr');
|
|
row.dataset.id = customer.id || customer.phoneNumber;
|
|
row.dataset.phone = customer.phoneNumber;
|
|
row.dataset.level = 'company-sea-pools'; // 添加等级信息便于事件委托处理
|
|
|
|
row.innerHTML = `
|
|
<td>${customer.company || '微信客户'}</td>
|
|
<td>${customer.region || '-'}</td>
|
|
<td>${customer.demand || '-'}</td>
|
|
<td>${customer.spec || '-'}</td>
|
|
<td>${customer.nickName || '-'}</td>
|
|
<td>${customer.phoneNumber || '-'}</td>
|
|
<td>${formatTime(customer.created_at) || '-'}</td>
|
|
<td>${formatTime(customer.updated_at) || '-'}</td>
|
|
<td>
|
|
<button class="action-btn view-details"
|
|
data-company-id="${customer.id || ''}"
|
|
data-phone="${customer.phoneNumber || ''}">
|
|
<i class="fas fa-eye"></i> 查看详情
|
|
</button>
|
|
</td>
|
|
`;
|
|
|
|
fragment.appendChild(row);
|
|
});
|
|
|
|
// 一次性添加到DOM
|
|
targetTbody.appendChild(fragment);
|
|
}
|
|
|
|
function fillCustomerDetails(customer) {
|
|
console.log('=== 开始填充客户详情 ===');
|
|
console.log('原始客户等级:', customer.level);
|
|
|
|
// 🔥 修复:在函数内部定义 normalizedLevel 变量
|
|
let normalizedLevel;
|
|
try {
|
|
// 标准化等级
|
|
normalizedLevel = standardizeCustomerLevel(customer.level);
|
|
console.log('等级标准化:', { original: customer.level, normalized: normalizedLevel });
|
|
} catch (error) {
|
|
console.error('等级标准化失败:', error);
|
|
normalizedLevel = customer.level || 'unclassified';
|
|
}
|
|
|
|
// 使用安全的方式设置文本内容,避免undefined错误
|
|
const setDetailText = (elementId, value) => {
|
|
const element = document.getElementById(elementId);
|
|
if (element) {
|
|
element.textContent = value || "-";
|
|
}
|
|
};
|
|
|
|
// 修复等级显示 - 使用标准化后的等级获取显示名称
|
|
const levelDisplayName = LEVEL_CONFIG.displayNames[normalizedLevel] || normalizedLevel;
|
|
|
|
// 设置所有详情字段
|
|
setDetailText('detail-id', customer.id);
|
|
setDetailText('detail-company', customer.company);
|
|
setDetailText('detail-region', customer.region);
|
|
setDetailText('detail-level', levelDisplayName); // 使用修复后的等级显示名称
|
|
setDetailText('detail-type', customer.type);
|
|
setDetailText('detail-demand', customer.demand);
|
|
setDetailText('detail-spec', customer.spec);
|
|
|
|
// 处理负责人信息
|
|
handleManagerInfoForDetails(customer);
|
|
|
|
setDetailText('detail-contact', customer.nickName);
|
|
setDetailText('detail-phone', customer.phoneNumber);
|
|
setDetailText('detail-wechat', customer.wechat);
|
|
setDetailText('detail-account', customer.account);
|
|
setDetailText('detail-account-number', customer.accountNumber);
|
|
setDetailText('detail-bank', customer.bank);
|
|
setDetailText('detail-address', customer.address);
|
|
setDetailText('detail-created-at', formatTime(customer.created_at));
|
|
setDetailText('detail-updated-at', formatTime(customer.updated_at));
|
|
|
|
// 处理公海需求部分显示
|
|
const publicSeaDemandSection = document.getElementById('publicSeaDemandSection');
|
|
const isPublicSeaCustomer = customer.level && (
|
|
customer.level === 'company-sea-pools' ||
|
|
customer.level === 'organization-sea-pools' ||
|
|
customer.level === 'department-sea-pools'
|
|
);
|
|
|
|
console.log('🔍 客户公海状态检查:', {
|
|
customerLevel: customer.level,
|
|
isPublicSeaCustomer,
|
|
hasProductItems: !!(customer.productItems && Array.isArray(customer.productItems) && customer.productItems.length > 0),
|
|
productItemsCount: customer.productItems ? customer.productItems.length : 0
|
|
});
|
|
|
|
if (isPublicSeaCustomer) {
|
|
// 🔥 关键修复:先重置管理器状态,确保干净的状态
|
|
publicSeaDemandManager.reset();
|
|
|
|
// 确保公海需求区域可见
|
|
document.getElementById('publicSeaDemandSection').style.display = 'block';
|
|
|
|
// 处理公海需求显示
|
|
publicSeaDemandManager.handleCustomerDetails(customer);
|
|
} else {
|
|
// 🔥 关键修复:隐藏公海需求区域并完全重置状态
|
|
document.getElementById('publicSeaDemandSection').style.display = 'none';
|
|
publicSeaDemandManager.reset();
|
|
|
|
// 🔥 额外保险:清除可能的UI残留
|
|
const demandFields = [
|
|
'detail-product-name', 'detail-variety', 'detail-specification',
|
|
'detail-quantity', 'detail-weight', 'detail-yolk'
|
|
];
|
|
demandFields.forEach(fieldId => {
|
|
const element = document.getElementById(fieldId);
|
|
if (element) {
|
|
element.textContent = '-';
|
|
}
|
|
const inputElement = document.getElementById(fieldId + '-input');
|
|
if (inputElement) {
|
|
inputElement.value = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log('=== 客户详情填充完成 ===');
|
|
}
|
|
|
|
// 处理查看详情时的负责人信息
|
|
function handleManagerInfoForDetails(customer) {
|
|
console.log('=== 处理查看详情负责人信息 ===');
|
|
console.log('当前客户负责人信息:', {
|
|
managerId: customer.managerId,
|
|
managercompany: customer.managercompany,
|
|
managerdepartment: customer.managerdepartment,
|
|
organization: customer.organization,
|
|
role: customer.role,
|
|
userName: customer.userName,
|
|
assistant: customer.assistant
|
|
});
|
|
|
|
// 只使用客户数据中的信息,不存在则显示为空
|
|
console.log('使用客户数据中的负责人信息');
|
|
const managerInfo = {
|
|
managerId: customer.managerId || '',
|
|
managercompany: customer.managercompany || '',
|
|
managerdepartment: customer.managerdepartment || '',
|
|
organization: customer.organization || '',
|
|
role: customer.role || '',
|
|
userName: customer.userName || '',
|
|
assistant: customer.assistant || ''
|
|
};
|
|
|
|
// 填充到详情页面
|
|
const setDetailText = (elementId, value) => {
|
|
const element = document.getElementById(elementId);
|
|
if (element) {
|
|
element.textContent = value || "-";
|
|
}
|
|
};
|
|
setDetailText('detail-managerId', managerInfo.managerId);
|
|
setDetailText('detail-managercompany', managerInfo.managercompany);
|
|
setDetailText('detail-managerdepartment', managerInfo.managerdepartment);
|
|
setDetailText('detail-organization', managerInfo.organization);
|
|
setDetailText('detail-role', managerInfo.role);
|
|
setDetailText('detail-manager', managerInfo.userName); // 注意:这里对应负责人字段
|
|
setDetailText('detail-assistant', managerInfo.assistant);
|
|
|
|
console.log('最终显示的负责人信息:', managerInfo);
|
|
}
|
|
|
|
// 重置编辑状态到只读模式
|
|
function resetEditState() {
|
|
console.log("状态切换中----");
|
|
const editButton = document.getElementById('editCustomerButton');
|
|
if (editButton) {
|
|
editButton.textContent = '修改';
|
|
editButton.classList.remove('confirm-mode');
|
|
}
|
|
makeDetailsReadOnly();
|
|
}
|
|
|
|
// 修改编辑按钮事件处理
|
|
function toggleEditMode() {
|
|
console.log("切换编辑模式");
|
|
const editButton = document.getElementById('editCustomerBtn');
|
|
const isEditing = editButton.textContent.includes('确认');
|
|
|
|
if (!isEditing) {
|
|
// 进入编辑模式
|
|
editButton.innerHTML = '<i class="fas fa-check"></i> 确认';
|
|
editButton.classList.add('confirm-mode');
|
|
makeDetailsEditable();
|
|
} else {
|
|
// 确认修改
|
|
if (confirm('确定要保存修改吗?')) {
|
|
saveCustomerChanges();
|
|
}
|
|
}
|
|
}
|
|
// 添加修改按钮事件监听(页面加载时执行)
|
|
console.log("初始化编辑按钮");
|
|
|
|
// 查找现有的编辑按钮
|
|
let editButton = document.getElementById('editCustomerBtn');
|
|
|
|
// 移除所有现有的事件监听器(通过克隆替换)
|
|
const newEditButton = editButton.cloneNode(true);
|
|
editButton.parentNode.replaceChild(newEditButton, editButton);
|
|
|
|
// 重新获取按钮引用
|
|
editButton = document.getElementById('editCustomerBtn');
|
|
|
|
// 绑定点击事件
|
|
editButton.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
console.log("编辑按钮被点击");
|
|
toggleEditMode();
|
|
});
|
|
console.log("编辑按钮初始化完成");
|
|
// 修复关闭按钮事件处理
|
|
function initModalEvents() {
|
|
const modal = document.getElementById('customerModal');
|
|
const closeModal = document.getElementById('closeModal');
|
|
|
|
if (!closeModal) {
|
|
console.error("未找到关闭按钮");
|
|
return;
|
|
}
|
|
|
|
// 重新绑定关闭事件
|
|
closeModal.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
const modal = document.getElementById('customerModal');
|
|
modal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
console.log("关闭模态框,重置编辑状态");
|
|
resetEditStateToInitial();
|
|
});
|
|
|
|
// 点击模态框外部关闭
|
|
modal.addEventListener('click', function (e) {
|
|
if (e.target === modal) {
|
|
modal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
resetEditStateToInitial();
|
|
}
|
|
});
|
|
|
|
// 初始化编辑按钮
|
|
initEditButton();
|
|
}
|
|
function resetEditStateToInitial() {
|
|
const editButton = document.getElementById('editCustomerBtn');
|
|
if (editButton) {
|
|
editButton.innerHTML = '<i class="fas fa-edit"></i> 编辑';
|
|
editButton.classList.remove('confirm-mode');
|
|
}
|
|
|
|
// 确保所有字段都恢复为只读状态
|
|
makeDetailsReadOnly();
|
|
|
|
// 清除任何可能存在的编辑状态数据
|
|
window.updatedCustomerData = null;
|
|
}
|
|
|
|
// 使详情可编辑
|
|
function makeDetailsEditable() {
|
|
const customer = window.currentCustomerData;
|
|
console.log('=== 进入编辑模式,处理负责人信息 ===');
|
|
|
|
// 🔥 关键修复:在进入编辑模式时确保当前需求被选中
|
|
if (customer.level && (
|
|
customer.level === 'company-sea-pools' ||
|
|
customer.level === 'organization-sea-pools' ||
|
|
customer.level === 'department-sea-pools'
|
|
)) {
|
|
console.log('🎯 编辑模式:确保公海需求选中状态');
|
|
publicSeaDemandManager.ensureCurrentDemandSelected();
|
|
}
|
|
// 将静态文本替换为输入框
|
|
replaceWithInput('detail-company', customer.company);
|
|
replaceWithInput('detail-region', customer.region);
|
|
replaceWithSelect('detail-level', customer.level);
|
|
replaceWithInput('detail-type', customer.type);
|
|
replaceWithMultiSelect('detail-demand', customer.demand);
|
|
replaceWithInput('detail-spec', customer.spec);
|
|
|
|
// // 负责人信息字段 - 允许修改
|
|
// replaceWithInput('detail-managerId', customer.managerId || '');
|
|
// replaceWithInput('detail-managercompany', customer.managercompany || '');
|
|
// replaceWithInput('detail-managerdepartment', customer.managerdepartment || '');
|
|
// replaceWithInput('detail-organization', customer.organization || '');
|
|
// replaceWithInput('detail-role', customer.role || '');
|
|
// replaceWithInput('detail-manager', customer.userName || ''); // 负责人
|
|
// replaceWithInput('detail-assistant', customer.assistant || '');
|
|
// 🔥 关键修改:根据客户等级和数据源决定负责人信息的显示
|
|
handleManagerInfoForEdit(customer);
|
|
|
|
replaceWithInput('detail-contact', customer.nickName);
|
|
replaceWithInput('detail-phone', customer.phoneNumber);
|
|
replaceWithInput('detail-wechat', customer.wechat);
|
|
replaceWithInput('detail-account', customer.account);
|
|
replaceWithInput('detail-account-number', customer.accountNumber);
|
|
replaceWithInput('detail-bank', customer.bank);
|
|
replaceWithInput('detail-address', customer.address);
|
|
|
|
// 处理公海池字段
|
|
if (window.currentCustomerData.level && (
|
|
window.currentCustomerData.level === 'company-sea-pools' ||
|
|
window.currentCustomerData.level === 'organization-sea-pools' ||
|
|
window.currentCustomerData.level === 'department-sea-pools'
|
|
)) {
|
|
// 公海需求字段变为可编辑
|
|
replaceWithInput('detail-product-name', window.currentCustomerData.productName);
|
|
replaceWithInput('detail-variety', window.currentCustomerData.variety);
|
|
replaceWithInput('detail-specification', window.currentCustomerData.specification);
|
|
replaceWithInput('detail-quantity', window.currentCustomerData.quantity);
|
|
replaceWithInput('detail-weight', window.currentCustomerData.grossWeight);
|
|
replaceWithInput('detail-yolk', window.currentCustomerData.yolk);
|
|
}
|
|
|
|
console.log('=== 编辑模式负责人信息设置完成 ===');
|
|
}
|
|
|
|
|
|
// 🔥 新增:编辑模式下处理负责人信息
|
|
function handleManagerInfoForEdit(customer) {
|
|
console.log('=== 编辑模式处理负责人信息 ===', {
|
|
level: customer.level,
|
|
dataSource: customer.dataSource,
|
|
apiUsed: customer.apiUsed
|
|
});
|
|
|
|
// 判断是否为公海池客户
|
|
const isPublicSeaCustomer = customer.level && (
|
|
customer.level === 'company-sea-pools' ||
|
|
customer.level === 'organization-sea-pools' ||
|
|
customer.level === 'department-sea-pools'
|
|
);
|
|
|
|
// 判断是否为wechat_app数据源
|
|
const isWechatDataSource = customer.dataSource === 'wechat' ||
|
|
customer.apiUsed === 'phone-api' ||
|
|
(customer.id && customer.id.startsWith('user_'));
|
|
|
|
console.log('客户类型判断:', {
|
|
isPublicSeaCustomer,
|
|
isWechatDataSource,
|
|
level: customer.level,
|
|
dataSource: customer.dataSource
|
|
});
|
|
|
|
let managerInfo;
|
|
|
|
if (isPublicSeaCustomer) {
|
|
// 🔥 公司公海池客户:使用当前登录信息
|
|
console.log('🔄 公海池客户,使用登录信息填充');
|
|
managerInfo = getLoginInfo();
|
|
} else if (isWechatDataSource) {
|
|
// 🔥 wechat_app数据源:使用客户本身的负责人信息
|
|
console.log('📱 wechat数据源,使用客户本身的负责人信息');
|
|
managerInfo = {
|
|
managerId: customer.managerId || '',
|
|
managercompany: customer.managercompany || '',
|
|
managerdepartment: customer.managerdepartment || '',
|
|
organization: customer.organization || '',
|
|
role: customer.role || '',
|
|
userName: customer.userName || '', // 负责人
|
|
assistant: customer.assistant || ''
|
|
};
|
|
|
|
// 如果客户本身的负责人信息全为空,则使用登录信息
|
|
const isAllEmpty = Object.values(managerInfo).every(value =>
|
|
!value || value === '无' || value.trim() === ''
|
|
);
|
|
|
|
if (isAllEmpty) {
|
|
console.log('⚠️ wechat客户负责人信息为空,使用登录信息');
|
|
managerInfo = getLoginInfo();
|
|
}
|
|
} else {
|
|
// 🔥 其他客户:使用客户本身的负责人信息
|
|
console.log('🏢 企业数据源,使用客户本身的负责人信息');
|
|
managerInfo = {
|
|
managerId: customer.managerId || '',
|
|
managercompany: customer.managercompany || '',
|
|
managerdepartment: customer.managerdepartment || '',
|
|
organization: customer.organization || '',
|
|
role: customer.role || '',
|
|
userName: customer.userName || '', // 负责人
|
|
assistant: customer.assistant || ''
|
|
};
|
|
}
|
|
|
|
console.log('最终使用的负责人信息:', managerInfo);
|
|
|
|
// 填充负责人信息到输入框
|
|
replaceWithInput('detail-managerId', managerInfo.managerId);
|
|
replaceWithInput('detail-managercompany', managerInfo.managercompany);
|
|
replaceWithInput('detail-managerdepartment', managerInfo.managerdepartment);
|
|
replaceWithInput('detail-organization', managerInfo.organization);
|
|
replaceWithInput('detail-role', managerInfo.role);
|
|
replaceWithInput('detail-manager', managerInfo.userName); // 负责人
|
|
replaceWithInput('detail-assistant', managerInfo.assistant);
|
|
}
|
|
|
|
|
|
|
|
function replaceWithMultiSelect(elementId, value) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
// 保存原始文本内容
|
|
element.dataset.originalContent = element.textContent;
|
|
|
|
// 创建多选下拉框容器
|
|
const container = document.createElement('div');
|
|
container.className = 'needs-select-container';
|
|
container.style.width = '100%';
|
|
|
|
// 创建触发器
|
|
const trigger = document.createElement('div');
|
|
trigger.className = 'needs-select-trigger detail-input';
|
|
trigger.id = elementId + '-trigger';
|
|
|
|
const placeholder = document.createElement('div');
|
|
placeholder.className = 'placeholder';
|
|
placeholder.textContent = '请选择需求';
|
|
|
|
const hiddenInput = document.createElement('input');
|
|
hiddenInput.type = 'hidden';
|
|
hiddenInput.id = elementId + '-input';
|
|
hiddenInput.value = value || '';
|
|
|
|
trigger.appendChild(placeholder);
|
|
trigger.appendChild(hiddenInput);
|
|
|
|
// 创建下拉菜单
|
|
const dropdown = document.createElement('div');
|
|
dropdown.className = 'needs-select-dropdown';
|
|
dropdown.id = elementId + '-dropdown';
|
|
|
|
// 选项数据
|
|
const options = [
|
|
{ value: 'pink-egg', text: '粉蛋' },
|
|
{ value: 'pink-three', text: '粉三' },
|
|
{ value: 'red-egg', text: '红蛋' },
|
|
{ value: 'green-shell', text: '绿壳' },
|
|
{ value: 'native-egg', text: '土鸡蛋' },
|
|
{ value: 'defective', text: '次品' },
|
|
{ value: 'white-shell', text: '白壳' }
|
|
];
|
|
|
|
// 添加全选选项
|
|
const selectAllOption = document.createElement('div');
|
|
selectAllOption.className = 'needs-select-option select-all';
|
|
selectAllOption.setAttribute('data-value', 'select-all');
|
|
selectAllOption.innerHTML = '<i class="check-icon"></i> 全选';
|
|
dropdown.appendChild(selectAllOption);
|
|
|
|
const divider = document.createElement('div');
|
|
divider.className = 'needs-select-divider';
|
|
dropdown.appendChild(divider);
|
|
|
|
// 添加选项
|
|
options.forEach(option => {
|
|
const optionElement = document.createElement('div');
|
|
optionElement.className = 'needs-select-option';
|
|
optionElement.setAttribute('data-value', option.value);
|
|
optionElement.textContent = option.text;
|
|
|
|
// 设置选中状态
|
|
if (value && value.includes(option.text)) {
|
|
optionElement.classList.add('selected');
|
|
}
|
|
|
|
dropdown.appendChild(optionElement);
|
|
});
|
|
|
|
container.appendChild(trigger);
|
|
container.appendChild(dropdown);
|
|
|
|
// 替换元素内容
|
|
element.innerHTML = '';
|
|
element.appendChild(container);
|
|
|
|
// 初始化多选下拉框交互
|
|
initNeedsSelect(elementId + '-trigger', elementId + '-dropdown', elementId + '-input');
|
|
}
|
|
// 新增:蛋黄单选下拉框
|
|
function replaceWithYolkSelect(elementId, value) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
// 保存原始文本内容
|
|
element.dataset.originalContent = element.textContent;
|
|
|
|
// 创建选择框
|
|
const select = document.createElement('select');
|
|
select.className = 'detail-select';
|
|
select.id = elementId + '-select';
|
|
|
|
// 蛋黄选项 - 修改:第一个选项作为提示,但不设置默认值
|
|
const yolkOptions = [
|
|
{ value: '', text: '请选择蛋黄类型', disabled: true, selected: !value }, // 只有当没有值时才默认选中提示
|
|
{ value: 'red-heart', text: '红心' },
|
|
{ value: 'yellow-heart', text: '黄心' },
|
|
{ value: 'two-color', text: '双色' }
|
|
];
|
|
|
|
// 添加选项
|
|
yolkOptions.forEach(option => {
|
|
const optionElement = document.createElement('option');
|
|
optionElement.value = option.value;
|
|
optionElement.textContent = option.text;
|
|
if (option.disabled) {
|
|
optionElement.disabled = true;
|
|
}
|
|
if (option.selected) {
|
|
optionElement.selected = true;
|
|
} else if (option.value === value) {
|
|
optionElement.selected = true;
|
|
}
|
|
select.appendChild(optionElement);
|
|
});
|
|
|
|
// 替换元素内容
|
|
element.innerHTML = '';
|
|
element.appendChild(select);
|
|
}
|
|
// 新增:初始化基本需求多选下拉框交互
|
|
function initNeedsSelect(triggerId, dropdownId, inputId) {
|
|
const trigger = document.getElementById(triggerId);
|
|
const dropdown = document.getElementById(dropdownId);
|
|
const hiddenInput = document.getElementById(inputId);
|
|
const options = dropdown.querySelectorAll('.needs-select-option:not(.select-all)');
|
|
const selectAllOption = dropdown.querySelector('.select-all');
|
|
const placeholder = trigger.querySelector('.placeholder');
|
|
|
|
let selectedValues = [];
|
|
let selectedTexts = [];
|
|
const MAX_SELECTIONS = 5;
|
|
|
|
// 从隐藏输入框初始化已选值
|
|
if (hiddenInput.value) {
|
|
selectedTexts = hiddenInput.value.split(',');
|
|
options.forEach(option => {
|
|
const text = option.textContent;
|
|
if (selectedTexts.includes(text)) {
|
|
option.classList.add('selected');
|
|
selectedValues.push(option.getAttribute('data-value'));
|
|
}
|
|
});
|
|
updateDisplay();
|
|
}
|
|
|
|
// 点击触发器切换下拉菜单
|
|
trigger.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
const isActive = dropdown.classList.contains('active');
|
|
|
|
// 关闭其他下拉框
|
|
document.querySelectorAll('.needs-select-dropdown.active').forEach(d => {
|
|
if (d !== dropdown) d.classList.remove('active');
|
|
});
|
|
|
|
if (!isActive) {
|
|
dropdown.classList.add('active');
|
|
trigger.classList.add('active');
|
|
}
|
|
});
|
|
|
|
// 选项点击事件
|
|
options.forEach(option => {
|
|
option.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
handleOptionSelect(this);
|
|
});
|
|
});
|
|
|
|
// 全选选项点击事件
|
|
selectAllOption.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
if (this.classList.contains('selected')) {
|
|
// 取消全选
|
|
clearAllSelections();
|
|
} else {
|
|
// 全选
|
|
selectAllPossible();
|
|
}
|
|
updateDisplay();
|
|
});
|
|
|
|
// 点击页面其他位置关闭下拉框
|
|
document.addEventListener('click', function () {
|
|
dropdown.classList.remove('active');
|
|
trigger.classList.remove('active');
|
|
});
|
|
|
|
// 阻止下拉框内部点击事件冒泡
|
|
dropdown.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
function handleOptionSelect(option) {
|
|
const value = option.getAttribute('data-value');
|
|
const text = option.textContent;
|
|
const index = selectedValues.indexOf(value);
|
|
|
|
if (index === -1) {
|
|
if (selectedValues.length >= MAX_SELECTIONS) {
|
|
showSelectionLimitAlert();
|
|
return;
|
|
}
|
|
selectedValues.push(value);
|
|
selectedTexts.push(text);
|
|
option.classList.add('selected');
|
|
} else {
|
|
selectedValues.splice(index, 1);
|
|
selectedTexts.splice(index, 1);
|
|
option.classList.remove('selected');
|
|
}
|
|
|
|
updateSelectAllState();
|
|
updateDisplay();
|
|
}
|
|
|
|
function clearAllSelections() {
|
|
selectedValues = [];
|
|
selectedTexts = [];
|
|
options.forEach(option => option.classList.remove('selected'));
|
|
selectAllOption.classList.remove('selected');
|
|
}
|
|
|
|
function selectAllPossible() {
|
|
clearAllSelections();
|
|
const selectableCount = Math.min(options.length, MAX_SELECTIONS);
|
|
|
|
Array.from(options).slice(0, selectableCount).forEach(option => {
|
|
const value = option.getAttribute('data-value');
|
|
const text = option.textContent;
|
|
selectedValues.push(value);
|
|
selectedTexts.push(text);
|
|
option.classList.add('selected');
|
|
});
|
|
selectAllOption.classList.add('selected');
|
|
}
|
|
|
|
function updateSelectAllState() {
|
|
const allSelected = Array.from(options).every(option =>
|
|
option.classList.contains('selected')
|
|
);
|
|
if (allSelected) {
|
|
selectAllOption.classList.add('selected');
|
|
} else {
|
|
selectAllOption.classList.remove('selected');
|
|
}
|
|
}
|
|
|
|
function updateDisplay() {
|
|
// 清除现有标签
|
|
const existingTags = trigger.querySelectorAll('.selected-tag');
|
|
existingTags.forEach(tag => tag.remove());
|
|
|
|
if (selectedValues.length === 0) {
|
|
placeholder.style.display = 'block';
|
|
} else {
|
|
placeholder.style.display = 'none';
|
|
|
|
// 添加标签
|
|
selectedTexts.forEach((text, index) => {
|
|
const tag = document.createElement('div');
|
|
tag.className = 'selected-tag';
|
|
tag.innerHTML = `
|
|
${text}
|
|
<span class="remove-tag" data-index="${index}">×</span>
|
|
`;
|
|
trigger.insertBefore(tag, hiddenInput);
|
|
|
|
// 绑定删除事件
|
|
tag.querySelector('.remove-tag').addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
removeTag(index);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 更新隐藏输入框
|
|
hiddenInput.value = selectedTexts.join(',');
|
|
}
|
|
|
|
function removeTag(index) {
|
|
const removedValue = selectedValues.splice(index, 1)[0];
|
|
selectedTexts.splice(index, 1);
|
|
|
|
options.forEach(option => {
|
|
if (option.getAttribute('data-value') === removedValue) {
|
|
option.classList.remove('selected');
|
|
}
|
|
});
|
|
|
|
updateSelectAllState();
|
|
updateDisplay();
|
|
}
|
|
|
|
function showSelectionLimitAlert() {
|
|
alert(`最多只能选择${MAX_SELECTIONS}项`);
|
|
}
|
|
}
|
|
|
|
|
|
// 辅助函数:将元素替换为品种下拉选择框
|
|
function replaceWithVarietySelect(elementId, value) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
// 保存原始文本内容
|
|
element.dataset.originalContent = element.textContent;
|
|
|
|
// 创建选择框
|
|
const select = document.createElement('select');
|
|
select.className = 'detail-select';
|
|
select.id = elementId + '-select';
|
|
|
|
// 品种选项列表
|
|
const varietyOptions = [
|
|
'罗曼粉', '伊莎粉', '罗曼灰', '海蓝灰', '海蓝褐',
|
|
'绿壳', '粉一', '粉三', '粉八', '京粉1号',
|
|
'京红', '京粉6号', '京粉3号', '农大系列',
|
|
'黑鸡土蛋', '双黄蛋', '大午金凤'
|
|
];
|
|
|
|
// 添加默认选项
|
|
const defaultOption = document.createElement('option');
|
|
defaultOption.value = '';
|
|
defaultOption.textContent = '请选择品种';
|
|
defaultOption.disabled = true;
|
|
defaultOption.selected = !value;
|
|
select.appendChild(defaultOption);
|
|
|
|
// 添加品种选项
|
|
varietyOptions.forEach(productName => {
|
|
const option = document.createElement('option');
|
|
option.value = productName;
|
|
option.textContent = productName;
|
|
if (productName === value) {
|
|
option.selected = true;
|
|
}
|
|
select.appendChild(option);
|
|
});
|
|
|
|
// 替换元素内容
|
|
element.innerHTML = '';
|
|
element.appendChild(select);
|
|
}
|
|
|
|
// 使详情只读
|
|
function makeDetailsReadOnly() {
|
|
console.log("查看中----======-----");
|
|
|
|
const customer = window.updatedCustomerData || window.currentCustomerData;
|
|
if (!customer) return;
|
|
|
|
// 基本需求字段:从多选下拉框恢复为文本
|
|
const demandInput = document.getElementById('detail-demand-input');
|
|
if (demandInput) {
|
|
customer.demand = demandInput.value;
|
|
}
|
|
// 将输入框替换回静态文本
|
|
replaceWithText('detail-company', customer.company);
|
|
replaceWithText('detail-region', customer.region);
|
|
|
|
// 恢复等级显示文本
|
|
const levelElement = document.getElementById('detail-level');
|
|
levelElement.textContent = LEVEL_CONFIG.displayNames[customer.level] || customer.level || "-";
|
|
|
|
replaceWithText('detail-type', customer.type);
|
|
replaceWithText('detail-demand', customer.demand);
|
|
replaceWithText('detail-spec', customer.spec);
|
|
replaceWithText('detail-manager', customer.manager);
|
|
replaceWithText('detail-department', customer.department);
|
|
replaceWithText('detail-assistant', customer.assistant);
|
|
replaceWithText('detail-contact', customer.nickName);
|
|
replaceWithText('detail-phone', customer.phoneNumber);
|
|
replaceWithText('detail-wechat', customer.wechat);
|
|
replaceWithText('detail-account', customer.account);
|
|
replaceWithText('detail-account-number', customer.accountNumber);
|
|
replaceWithText('detail-bank', customer.bank);
|
|
replaceWithText('detail-address', customer.address);
|
|
replaceWithText('detail-created-at', formatTime(customer.created_at));
|
|
replaceWithText('detail-updated-at', formatTime(customer.updated_at));
|
|
|
|
// 处理公海池字段
|
|
if (customer.level === 'public-sea') {
|
|
// 修改:将品种和蛋黄字段恢复为只读
|
|
replaceWithText('detail-product-name', customer.productName);
|
|
replaceWithText('detail-variety', customer.variety);
|
|
replaceWithText('detail-specification', customer.specification);
|
|
replaceWithText('detail-quantity', customer.quantity);
|
|
replaceWithText('detail-weight', customer.grossWeight);
|
|
replaceWithText('detail-yolk', customer.yolk);
|
|
}
|
|
}
|
|
|
|
// 辅助函数:将元素替换为输入框
|
|
function replaceWithInput(elementId, value) {
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
// 保存原始文本内容
|
|
element.dataset.originalContent = element.textContent;
|
|
|
|
// 创建输入框
|
|
const input = document.createElement('input');
|
|
input.type = 'text';
|
|
input.value = value || '';
|
|
input.className = 'detail-input';
|
|
input.id = elementId + '-input';
|
|
|
|
// 替换元素内容
|
|
element.innerHTML = '';
|
|
element.appendChild(input);
|
|
}
|
|
// 辅助函数:将元素替换为下拉选择框(用于客户等级)
|
|
// 辅助函数:将元素替换为下拉选择框(用于客户等级)
|
|
function replaceWithSelect(elementId, value) {
|
|
console.log("进入修改中----辅助1");
|
|
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
// 保存原始文本内容
|
|
element.dataset.originalContent = element.textContent;
|
|
|
|
// 创建选择框
|
|
const select = document.createElement('select');
|
|
select.className = 'detail-select';
|
|
select.id = elementId + '-select';
|
|
|
|
// 🔥 修复:在函数内部定义 normalizedValue
|
|
let normalizedValue;
|
|
try {
|
|
normalizedValue = standardizeCustomerLevel(value);
|
|
console.log('下拉框等级标准化:', { original: value, normalized: normalizedValue });
|
|
} catch (error) {
|
|
console.error('下拉框等级标准化失败:', error);
|
|
normalizedValue = value || 'unclassified';
|
|
}
|
|
|
|
// 添加选项
|
|
const levelOptions = {
|
|
'important': '重要客户',
|
|
'normal': '普通客户',
|
|
'low-value': '低价值客户',
|
|
'logistics': '物流客户',
|
|
'unclassified': '未分级客户',
|
|
'company-sea-pools': '公司公海池',
|
|
'organization-sea-pools': '组织公海池',
|
|
'department-sea-pools': '部门公海池'
|
|
};
|
|
|
|
Object.keys(levelOptions).forEach(level => {
|
|
const option = document.createElement('option');
|
|
option.value = level;
|
|
option.textContent = levelOptions[level];
|
|
// 修复:使用标准化后的值进行比较
|
|
if (level === normalizedValue) {
|
|
option.selected = true;
|
|
}
|
|
select.appendChild(option);
|
|
});
|
|
|
|
// 替换元素内容
|
|
element.innerHTML = '';
|
|
element.appendChild(select);
|
|
}
|
|
|
|
// 辅助函数:将输入框替换回静态文本
|
|
function replaceWithText(elementId, value) {
|
|
console.log("进入修改中----辅助2");
|
|
|
|
const element = document.getElementById(elementId);
|
|
if (!element) return;
|
|
|
|
// 恢复原始内容
|
|
element.textContent = value || "-";
|
|
}
|
|
|
|
|
|
// 修改保存客户信息的函数
|
|
async function saveCustomerChanges() {
|
|
if (!currentCustomerId || !window.currentCustomerData) return;
|
|
|
|
try {
|
|
const updatedData = collectUpdatedData();
|
|
const now = new Date();
|
|
updatedData.updated_at = formatToLocalDateTime(now, true);
|
|
|
|
// 根据客户等级决定使用哪个接口
|
|
const customerLevel = window.currentCustomerData.level;
|
|
const isPublicSeaCustomer = customerLevel && (
|
|
customerLevel === 'company-sea-pools' ||
|
|
customerLevel === 'organization-sea-pools' ||
|
|
customerLevel === 'department-sea-pools'
|
|
);
|
|
|
|
console.log(`客户等级: ${customerLevel}, 是否公海池: ${isPublicSeaCustomer}`);
|
|
console.log('更新数据:', updatedData);
|
|
|
|
let response;
|
|
|
|
if (isPublicSeaCustomer) {
|
|
// 🔥 添加公海池更新的详细日志
|
|
console.log('🎯 使用公海池精确更新接口 - 销售端');
|
|
console.log('📦 购物车项精确更新数据:', {
|
|
targetProductItemId: updatedData.targetProductItemId,
|
|
updateCartItemData: updatedData.updateCartItemData
|
|
});
|
|
response = await savePhoneBasedCustomer(updatedData);
|
|
} else {
|
|
console.log('🏢 使用企业数据源更新接口');
|
|
response = await saveCompanyBasedCustomer(updatedData);
|
|
}
|
|
|
|
// 处理响应
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
if (result.success) {
|
|
handleSaveSuccess(updatedData, isPublicSeaCustomer ? 'phone' : 'company');
|
|
} else {
|
|
throw new Error(result.message || '更新失败');
|
|
}
|
|
} else {
|
|
const errorText = await response.text();
|
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('更新客户信息失败:', error);
|
|
alert('更新客户信息失败: ' + error.message);
|
|
resetEditStateToInitial();
|
|
}
|
|
}
|
|
|
|
// 确保正确识别数据源
|
|
function determineDataSource(customerData) {
|
|
if (customerData.level && (
|
|
customerData.level === 'company-sea-pools' ||
|
|
customerData.level === 'organization-sea-pools' ||
|
|
customerData.level === 'department-sea-pools'
|
|
)) {
|
|
return 'phone'; // 公海池客户
|
|
} else if (customerData.id && customerData.id.startsWith('user_')) {
|
|
return 'wechat'; // 微信数据源
|
|
} else {
|
|
return 'company'; // 企业数据源
|
|
}
|
|
}
|
|
|
|
// 保存电话号码数据源的客户信息
|
|
async function savePhoneBasedCustomer(updatedData) {
|
|
|
|
console.log('电话号码数据源提交的数据:', updatedData);
|
|
|
|
// 使用新端口 - 请替换为实际的新端口URL
|
|
const apiUrl = appendAuthParams(`${API_BASE_URL}/pool/phone-customers/update`);
|
|
|
|
return await fetch(apiUrl, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(updatedData)
|
|
});
|
|
}
|
|
|
|
// 保存公司ID数据源的客户信息
|
|
async function saveCompanyBasedCustomer(updatedData) {
|
|
// 提交所有字段
|
|
console.log('公司ID数据源提交的数据:', updatedData);
|
|
|
|
// 使用原端口
|
|
const apiUrl = appendAuthParams(`${API_BASE_URL}/pool/customers/update`);
|
|
|
|
|
|
return await fetch(apiUrl, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(updatedData)
|
|
});
|
|
}
|
|
|
|
// 在保存时收集数据
|
|
function collectUpdatedData() {
|
|
const updatedData = {};
|
|
const customer = window.currentCustomerData;
|
|
|
|
// 设置数据源标识
|
|
if (customer.id && customer.id.startsWith('user_')) {
|
|
updatedData.dataSource = 'wechat';
|
|
} else {
|
|
updatedData.dataSource = 'default';
|
|
}
|
|
|
|
// 收集基本标识字段
|
|
updatedData.id = currentCustomerId;
|
|
updatedData.phoneNumber = getInputValue('detail-phone-input') || customer.phoneNumber;
|
|
|
|
// 企业基本信息
|
|
updatedData.company = getInputValue('detail-company-input') || customer.company;
|
|
updatedData.region = getInputValue('detail-region-input') || customer.region;
|
|
updatedData.level = getSelectValue('detail-level-select') || customer.level;
|
|
updatedData.type = getInputValue('detail-type-input') || customer.type;
|
|
|
|
// 处理基本需求(多选下拉框)
|
|
const demandInput = document.getElementById('detail-demand-input');
|
|
if (demandInput) {
|
|
updatedData.demand = demandInput.value;
|
|
} else {
|
|
updatedData.demand = customer.demand;
|
|
}
|
|
|
|
updatedData.spec = getInputValue('detail-spec-input') || customer.spec;
|
|
|
|
// 负责人信息 - 收集所有字段
|
|
updatedData.managerId = getInputValue('detail-managerId-input') || customer.managerId;
|
|
updatedData.managercompany = getInputValue('detail-managercompany-input') || customer.managercompany;
|
|
updatedData.managerdepartment = getInputValue('detail-managerdepartment-input') || customer.managerdepartment;
|
|
updatedData.organization = getInputValue('detail-organization-input') || customer.organization;
|
|
updatedData.role = getInputValue('detail-role-input') || customer.role;
|
|
updatedData.userName = getInputValue('detail-manager-input') || customer.userName; // 负责人
|
|
updatedData.assistant = getInputValue('detail-assistant-input') || customer.assistant;
|
|
|
|
// 联系人信息
|
|
updatedData.nickName = getInputValue('detail-contact-input') || customer.nickName;
|
|
updatedData.wechat = getInputValue('detail-wechat-input') || customer.wechat;
|
|
updatedData.account = getInputValue('detail-account-input') || customer.account;
|
|
updatedData.accountNumber = getInputValue('detail-account-number-input') || customer.accountNumber;
|
|
updatedData.bank = getInputValue('detail-bank-input') || customer.bank;
|
|
updatedData.address = getInputValue('detail-address-input') || customer.address;
|
|
|
|
// 🔥 关键修复:确保当前需求被选中 - 销售端
|
|
if (customer.level && (
|
|
customer.level === 'company-sea-pools' ||
|
|
customer.level === 'organization-sea-pools' ||
|
|
customer.level === 'department-sea-pools'
|
|
)) {
|
|
// 确保当前有选中的需求
|
|
publicSeaDemandManager.ensureCurrentDemandSelected();
|
|
|
|
// 获取精确更新字段
|
|
const preciseFields = publicSeaDemandManager.getPreciseUpdateFields();
|
|
|
|
console.log('🎯 精确更新字段 - 销售端:', preciseFields);
|
|
|
|
if (preciseFields.targetProductItemId && preciseFields.updateProductItemData) {
|
|
// 设置精确更新字段
|
|
updatedData.targetProductItemId = preciseFields.targetProductItemId;
|
|
updatedData.updateProductItemData = preciseFields.updateProductItemData;
|
|
|
|
console.log('✅ 使用精确更新 - 销售端:', {
|
|
targetProductItemId: updatedData.targetProductItemId,
|
|
productName: updatedData.updateProductItemData?.productName
|
|
});
|
|
} else {
|
|
console.warn('⚠️ 无法获取精确更新字段,使用向后兼容方式 - 销售端');
|
|
|
|
// 向后兼容:使用单个字段
|
|
updatedData.productName = getInputValue('detail-product-name-input') || customer.productName;
|
|
updatedData.variety = getInputValue('detail-variety-input') || customer.variety;
|
|
updatedData.specification = getInputValue('detail-specification-input') || customer.specification;
|
|
updatedData.quantity = safeParseInt(getInputValue('detail-quantity-input'), customer.quantity);
|
|
updatedData.grossWeight = getInputValue('detail-weight-input') || customer.grossWeight;
|
|
updatedData.yolk = getInputValue('detail-yolk-input') || customer.yolk;
|
|
}
|
|
}
|
|
|
|
console.log('收集到的更新数据 - 销售端:', updatedData);
|
|
return updatedData;
|
|
}
|
|
|
|
// 🔥 新增:数据类型转换辅助函数
|
|
function safeParseInt(value, defaultValue = 0) {
|
|
if (value === null || value === undefined || value === '') return defaultValue;
|
|
const parsed = parseInt(value);
|
|
return isNaN(parsed) ? defaultValue : parsed;
|
|
}
|
|
|
|
function safeParseFloat(value, defaultValue = 0) {
|
|
if (value === null || value === undefined || value === '') return defaultValue;
|
|
const parsed = parseFloat(value);
|
|
return isNaN(parsed) ? defaultValue : parsed;
|
|
}
|
|
|
|
// 获取输入框值的辅助函数
|
|
function getInputValue(elementId) {
|
|
const element = document.getElementById(elementId);
|
|
return element ? element.value : null;
|
|
}
|
|
|
|
function getSelectValue(elementId) {
|
|
const element = document.getElementById(elementId);
|
|
return element ? element.value : null;
|
|
}
|
|
|
|
// 日志工具函数
|
|
const DemandLogger = {
|
|
log: function (message, data = null) {
|
|
const timestamp = new Date(new Date().getTime() + (8 * 60 * 60 * 1000)).toISOString();
|
|
console.log(`[${timestamp}] 公海需求: ${message}`, data || '');
|
|
},
|
|
|
|
error: function (message, error = null) {
|
|
const timestamp = new Date(new Date().getTime() + (8 * 60 * 60 * 1000)).toISOString();
|
|
console.error(`[${timestamp}] 公海需求错误: ${message}`, error || '');
|
|
},
|
|
|
|
warn: function (message, data = null) {
|
|
const timestamp = new Date(new Date().getTime() + (8 * 60 * 60 * 1000)).toISOString();
|
|
console.warn(`[${timestamp}] 公海需求警告: ${message}`, data || '');
|
|
},
|
|
|
|
debug: function (message, data = null) {
|
|
if (console.debug) {
|
|
const timestamp = new Date(new Date().getTime() + (8 * 60 * 60 * 1000)).toISOString();
|
|
console.debug(`[${timestamp}] 公海需求调试: ${message}`, data || '');
|
|
}
|
|
}
|
|
};
|
|
|
|
// 在关键位置添加日志
|
|
// 例如在handleCustomerDetails方法中:
|
|
DemandLogger.log('处理客户公海需求', {
|
|
customerId: customer.id,
|
|
productItemsCount: customer.productItems ? customer.productItems.length : 0,
|
|
level: customer.level
|
|
});
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
console.log('页面加载完成,初始化公海需求功能 - 销售端');
|
|
|
|
// 确保弹窗HTML已添加到页面
|
|
if (!document.getElementById('publicSeaDemandsModal')) {
|
|
console.log('添加公海需求选择弹窗到页面');
|
|
// 这里可以动态添加弹窗HTML,或者确保在静态HTML中已经包含
|
|
}
|
|
});
|
|
|
|
// 构建API请求
|
|
function buildApiRequest(dataSource, updatedData) {
|
|
const identifier = dataSource === 'wechat' ?
|
|
(updatedData.phoneNumber || currentCustomerId) :
|
|
(updatedData.id || currentCustomerId);
|
|
|
|
const apiUrl = dataSource === 'wechat' ?
|
|
`${API_BASE_URL}/pool/wechat-customers/${encodeURIComponent(identifier)}` :
|
|
`${API_BASE_URL}/pool/customers/${encodeURIComponent(identifier)}`;
|
|
|
|
// wechat数据源特殊处理:确保电话号码一致性
|
|
if (dataSource === 'wechat') {
|
|
updatedData.phoneNumber = identifier;
|
|
}
|
|
|
|
return {
|
|
apiUrl,
|
|
requestMethod: 'PUT',
|
|
requestBody: JSON.stringify(updatedData)
|
|
};
|
|
}
|
|
|
|
// 修改保存客户信息后的处理函数
|
|
function handleSaveSuccess(updatedData, dataSource) {
|
|
window.updatedCustomerData = updatedData;
|
|
resetEditStateToInitial();
|
|
|
|
// 根据数据源显示不同的成功消息
|
|
if (dataSource === 'phone') {
|
|
alert('🎉 公海池客户信息已成功更新!');
|
|
} else {
|
|
alert('🎉 客户信息已成功更新!');
|
|
}
|
|
|
|
console.log('更新成功,刷新客户列表... - 销售端');
|
|
|
|
// 🔥 更新缓存中的客户数据
|
|
updateCustomerCache(updatedData);
|
|
|
|
// 获取当前激活的等级标签,刷新对应数据
|
|
const activeLevelTab = document.querySelector('.level-tab.active');
|
|
if (activeLevelTab) {
|
|
const level = activeLevelTab.getAttribute('data-level');
|
|
const levelMap = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'low': 'low-value',
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified',
|
|
'public-sea': 'public-sea',
|
|
'all': 'all'
|
|
};
|
|
|
|
const backendLevel = levelMap[level] || level;
|
|
refreshCustomerData(backendLevel);
|
|
} else {
|
|
// 默认刷新全部客户
|
|
refreshCustomerData('all');
|
|
}
|
|
|
|
// 🔥 刷新控制面板中的最近客户数据
|
|
// 移除自动刷新调用,避免页面闪动
|
|
|
|
resetEditStateToInitial();
|
|
}
|
|
|
|
// 🔥 新增:更新客户缓存数据函数
|
|
function updateCustomerCache(updatedData) {
|
|
console.log('更新客户缓存数据...', { customerId: updatedData.id || updatedData.phoneNumber });
|
|
|
|
// 更新全局客户数据对象
|
|
if (window.allCustomersData && Array.isArray(window.allCustomersData)) {
|
|
const index = window.allCustomersData.findIndex(c =>
|
|
c.id === updatedData.id || c.phoneNumber === updatedData.phoneNumber || c.phone === updatedData.phoneNumber
|
|
);
|
|
if (index !== -1) {
|
|
window.allCustomersData[index] = { ...window.allCustomersData[index], ...updatedData };
|
|
console.log('✅ 成功更新全局客户数据中的客户信息');
|
|
}
|
|
}
|
|
|
|
// 更新各等级客户缓存
|
|
const cacheKeys = ['important', 'normal', 'low-value', 'logistics', 'unclassified', 'all'];
|
|
cacheKeys.forEach(key => {
|
|
if (customerCache && customerCache[key] && Array.isArray(customerCache[key].data)) {
|
|
const index = customerCache[key].data.findIndex(c =>
|
|
c.id === updatedData.id || c.phoneNumber === updatedData.phoneNumber || c.phone === updatedData.phoneNumber
|
|
);
|
|
if (index !== -1) {
|
|
customerCache[key].data[index] = { ...customerCache[key].data[index], ...updatedData };
|
|
console.log(`✅ 成功更新${key}等级缓存中的客户信息`);
|
|
}
|
|
}
|
|
});
|
|
|
|
// 更新window.customers对象(如果存在)
|
|
if (window.customers && Array.isArray(window.customers)) {
|
|
const index = window.customers.findIndex(c =>
|
|
c.id === updatedData.id || c.phoneNumber === updatedData.phoneNumber || c.phone === updatedData.phoneNumber
|
|
);
|
|
if (index !== -1) {
|
|
window.customers[index] = { ...window.customers[index], ...updatedData };
|
|
console.log('✅ 成功更新window.customers中的客户信息');
|
|
}
|
|
}
|
|
}
|
|
|
|
// 修改等级切换功能,确保时间筛选在切换时被清除
|
|
function setupLevelTabs() {
|
|
const levelTabs = document.querySelectorAll('.level-tab');
|
|
levelTabs.forEach(tab => {
|
|
tab.addEventListener('click', function() {
|
|
const level = this.getAttribute('data-level');
|
|
console.log(`=== 点击等级标签: ${level} ===`);
|
|
|
|
levelTabs.forEach(t => t.classList.remove('active'));
|
|
this.classList.add('active');
|
|
|
|
currentLevel = level;
|
|
currentPage = 1;
|
|
|
|
// 隐藏所有表格和分页控件
|
|
const allTableBodies = [
|
|
'important-customers', 'normal-customers', 'low-customers',
|
|
'logistics-customers', 'unclassified-customers', 'all-customers',
|
|
'company-sea-pools-customers', 'organization-sea-pools-customers', 'department-sea-pools-customers'
|
|
];
|
|
|
|
allTableBodies.forEach(id => {
|
|
const element = document.getElementById(id);
|
|
if (element) {
|
|
element.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// 隐藏所有分页控件
|
|
const allPaginations = [
|
|
'all-customers-pagination', 'important-pagination', 'normal-pagination',
|
|
'low-pagination', 'logistics-pagination', 'unclassified-pagination',
|
|
'company-sea-pools-pagination', 'organization-sea-pools-pagination', 'department-sea-pools-pagination'
|
|
];
|
|
|
|
allPaginations.forEach(id => {
|
|
const element = document.getElementById(id);
|
|
if (element) {
|
|
element.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
const levelMap = {
|
|
'important': 'important',
|
|
'normal': 'normal',
|
|
'low': 'low-value',
|
|
'logistics': 'logistics',
|
|
'unclassified': 'unclassified',
|
|
'company-sea-pools': 'company-sea-pools',
|
|
'organization-sea-pools': 'organization-sea-pools',
|
|
'department-sea-pools': 'department-sea-pools',
|
|
'all': 'all'
|
|
};
|
|
|
|
const backendLevel = levelMap[level] || level;
|
|
|
|
if (level === 'all') {
|
|
// 全部客户的处理 - 修复版本
|
|
const allPagination = document.getElementById('all-customers-pagination');
|
|
const allTable = document.getElementById('all-customers');
|
|
|
|
if (allPagination) allPagination.style.display = 'flex';
|
|
if (allTable) allTable.style.display = 'table-row-group';
|
|
|
|
// 强制重置到第一页
|
|
allCustomersCurrentPage = 1;
|
|
|
|
// 使用缓存数据立即渲染,但避免重复渲染
|
|
const cachedData = window.customerCache.get('all');
|
|
if (cachedData) {
|
|
console.log('✅ 使用缓存数据立即渲染全部客户');
|
|
// 直接设置数据并渲染,不经过复杂的处理流程
|
|
allCustomersData = cachedData;
|
|
renderAllCustomersTable();
|
|
updateAllCustomersPaginationInfo();
|
|
}
|
|
|
|
// 延迟异步刷新数据,避免与立即渲染冲突
|
|
setTimeout(() => {
|
|
refreshCustomerData('all');
|
|
}, 300); // 增加延迟,确保第一次渲染完成
|
|
|
|
} else {
|
|
// 单个等级的处理
|
|
const levelPagination = document.getElementById(`${level}-pagination`);
|
|
const levelTable = document.getElementById(`${level}-customers`);
|
|
|
|
if (levelPagination) {
|
|
levelPagination.style.display = 'flex';
|
|
}
|
|
|
|
if (levelTable) {
|
|
levelTable.style.display = 'table-row-group';
|
|
}
|
|
|
|
// 使用缓存数据立即渲染
|
|
const cachedData = window.customerCache.get(backendLevel);
|
|
if (cachedData) {
|
|
console.log(`✅ 使用缓存数据立即渲染 ${level}`);
|
|
optimizedProcessFilteredCustomers(cachedData, backendLevel);
|
|
}
|
|
|
|
// 刷新数据
|
|
refreshCustomerData(backendLevel);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
// 添加调试函数检查客户等级分布
|
|
function debugCustomerLevelDistribution(customers, level) {
|
|
const levelCounts = {};
|
|
customers.forEach(customer => {
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
levelCounts[customerLevel] = (levelCounts[customerLevel] || 0) + 1;
|
|
});
|
|
|
|
console.log(`🔍 ${level} 等级数据分布:`, levelCounts);
|
|
|
|
// 检查是否有错误分类的客户
|
|
const invalidCustomers = customers.filter(customer => {
|
|
const customerLevel = standardizeCustomerLevel(customer.level);
|
|
return customerLevel !== level && level !== 'all';
|
|
});
|
|
|
|
if (invalidCustomers.length > 0) {
|
|
console.warn(`⚠️ ${level} 中发现错误分类的客户:`, invalidCustomers.map(c => ({
|
|
id: c.id,
|
|
company: c.company,
|
|
actualLevel: c.level,
|
|
standardizedLevel: standardizeCustomerLevel(c.level)
|
|
})));
|
|
}
|
|
}
|
|
|
|
|
|
// 新增:刷新基于电话号码的客户数据
|
|
function refreshPhoneBasedCustomers() {
|
|
fetch(`/pool/customers/by-phone?phone=${phoneNumber}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
console.log("获取到的客户信息:", data);
|
|
});
|
|
}
|
|
// 获取蛋黄值
|
|
function getYolkValue() {
|
|
const yolkSelect = document.getElementById('detail-yolk-select');
|
|
if (yolkSelect) {
|
|
const selectedOption = yolkSelect.options[yolkSelect.selectedIndex];
|
|
return selectedOption.textContent === '请选择蛋黄类型' ? '' : selectedOption.value;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
|
|
|
|
// 判断是否为wechat数据源
|
|
function isWechatDataSource(customerData) {
|
|
return customerData.dataSource === 'wechat' ||
|
|
!customerData.company ||
|
|
(customerData.id && customerData.id.startsWith('WECHAT')) ||
|
|
(customerData.phoneNumber && !customerData.id);
|
|
}
|
|
|
|
// 获取数据源特定的API基础路径
|
|
function getDataSourceApiBase(dataSource) {
|
|
return dataSource === 'wechat' ? `${API_BASE_URL}/pool/wechat-customers` : `${API_BASE_URL}/pool/customers`;
|
|
}
|
|
|
|
// 获取数据源特定的标识符
|
|
function getDataSourceIdentifier(dataSource, customerId, customerData) {
|
|
if (dataSource === 'wechat') {
|
|
return customerData.phoneNumber || customerId;
|
|
} else {
|
|
return customerData.id || customerId;
|
|
}
|
|
}
|
|
// 刷新微信客户数据
|
|
function refreshWechatCustomerData() {
|
|
// 使用统一的客户接口,而不是单独的微信客户接口
|
|
fetch(`${API_BASE_URL}/pool/customers`)
|
|
.then(response => response.json())
|
|
.then(result => {
|
|
if (result.success) {
|
|
const allCustomers = Object.values(result.data || {});
|
|
|
|
// 过滤出微信客户(通过特定特征判断)
|
|
const wechatCustomers = allCustomers.filter(customer =>
|
|
!customer.id || customer.id.startsWith('WECHAT') || !customer.company
|
|
);
|
|
|
|
console.log('微信客户数据:', wechatCustomers);
|
|
mergeWechatCustomers(wechatCustomers);
|
|
} else {
|
|
console.warn('获取客户数据失败:', result.message);
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('刷新客户数据失败:', error);
|
|
});
|
|
}
|
|
|
|
// 添加分页功能测试函数
|
|
function testPaginationFunctionality() {
|
|
console.log('测试分页功能...');
|
|
|
|
// 测试全部客户分页
|
|
const allCustomersPagination = document.getElementById('all-customers-pagination');
|
|
if (allCustomersPagination) {
|
|
console.log('✅ 全部客户分页控件存在');
|
|
|
|
// 测试分页按钮
|
|
const prevBtn = document.getElementById('all-customers-prev-page');
|
|
const nextBtn = document.getElementById('all-customers-next-page');
|
|
const pageInput = document.getElementById('all-customers-page-input');
|
|
const goBtn = document.getElementById('all-customers-page-go-btn');
|
|
|
|
if (prevBtn && nextBtn && pageInput && goBtn) {
|
|
console.log('✅ 全部客户分页所有控件都存在');
|
|
} else {
|
|
console.log('❌ 全部客户分页控件缺失');
|
|
}
|
|
}
|
|
|
|
// 测试默认分页
|
|
const defaultPagination = document.getElementById('customers-pagination');
|
|
if (defaultPagination) {
|
|
console.log('✅ 默认分页控件存在');
|
|
}
|
|
|
|
// 测试等级切换
|
|
const levelTabs = document.querySelectorAll('.level-tab');
|
|
if (levelTabs.length > 0) {
|
|
console.log(`✅ 找到 ${levelTabs.length} 个等级标签`);
|
|
}
|
|
}
|
|
|
|
// 修改合并微信客户到未分级客户表格的函数
|
|
function mergeWechatCustomers(wechatCustomers) {
|
|
const unclassifiedTbody = document.getElementById('unclassified-customers');
|
|
if (!unclassifiedTbody) return;
|
|
|
|
// 获取现有的未分级客户
|
|
const existingRows = Array.from(unclassifiedTbody.querySelectorAll('tr'));
|
|
const existingWechatRows = existingRows.filter(row => row.dataset.source === 'wechat');
|
|
|
|
// 移除现有的微信客户行
|
|
existingWechatRows.forEach(row => row.remove());
|
|
|
|
// 只添加等级为unclassified的微信客户
|
|
const unclassifiedWechatCustomers = wechatCustomers.filter(customer =>
|
|
customer.level === 'unclassified' && isAllCustomer(customer) // 确保不是公海池客户
|
|
);
|
|
|
|
// 添加新的微信客户行
|
|
unclassifiedWechatCustomers.forEach(customer => {
|
|
const row = document.createElement('tr');
|
|
row.dataset.id = customer.phoneNumber; // 使用电话号码作为ID
|
|
row.dataset.source = 'wechat'; // 标记数据源
|
|
|
|
// 为微信客户设置特殊样式
|
|
row.style.backgroundColor = 'rgba(76, 175, 80, 0.1)';
|
|
|
|
row.innerHTML = `
|
|
<td>${customer.company || '微信客户'}</td>
|
|
<td>${customer.region || '-'}</td>
|
|
<td>${customer.demand || '-'}</td>
|
|
<td>${customer.spec || '-'}</td>
|
|
<td>${customer.nickName || '-'}</td>
|
|
<td>${customer.phoneNumber || '-'}</td>
|
|
<td>${formatTime(customer.created_at) || '-'}</td>
|
|
<td>${formatTime(customer.updated_at) || '-'}</td>
|
|
<td>
|
|
<button class="action-btn view-details" data-id="${customer.phoneNumber}" data-source="wechat">
|
|
查看详情
|
|
</button>
|
|
</td>
|
|
`;
|
|
|
|
// 绑定查看详情事件
|
|
const viewButton = row.querySelector('.view-details');
|
|
viewButton.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
viewCustomerDetails(customer.id, customer.phoneNumber);
|
|
});
|
|
|
|
row.addEventListener('click', () => {
|
|
viewCustomerDetails(customer.id, customer.phoneNumber);
|
|
});
|
|
|
|
unclassifiedTbody.appendChild(row);
|
|
});
|
|
|
|
// 更新分页显示
|
|
if (currentLevel === 'unclassified') {
|
|
safeUpdatePagination();
|
|
}
|
|
}
|
|
|
|
|
|
// 辅助函数:获取输入框的值
|
|
function getInputValue(inputId) {
|
|
console.log("进入状态切换中----");
|
|
|
|
const input = document.getElementById(inputId);
|
|
return input ? input.value.trim() : '';
|
|
}
|
|
// 辅助函数:获取下拉框的值
|
|
function getSelectValue(selectId) {
|
|
const select = document.getElementById(selectId);
|
|
return select ? select.value : '';
|
|
}
|
|
// 修改绑定事件的处理,支持不同数据源的客户
|
|
document.addEventListener('click', function (e) {
|
|
if (e.target.classList.contains('view-details')) {
|
|
const customerId = e.target.getAttribute('data-id');
|
|
const dataSource = e.target.getAttribute('data-source');
|
|
|
|
|
|
if (customerId) {
|
|
e.stopPropagation();
|
|
// 存储数据源信息
|
|
window.expectedDataSource = dataSource;
|
|
viewCustomerDetails(customerId);
|
|
}
|
|
}
|
|
});
|
|
|
|
// 绑定行点击事件
|
|
document.querySelectorAll('#customers-table tbody tr, #recent-customers-table tbody tr').forEach(row => {
|
|
row.addEventListener('click', function () {
|
|
const customerId = this.getAttribute('data-id');
|
|
// 获取存储在行上的电话号码
|
|
const phoneNumber = this.getAttribute('data-phone') || this.querySelector('td:nth-child(6)')?.textContent || '';
|
|
|
|
if (customerId) {
|
|
// 检查customerId是公司ID还是电话号码
|
|
// 公司ID通常以"CUST-"开头,或者是其他特定格式
|
|
const isCompanyId = customerId.startsWith('CUST-') || customerId.length > 11;
|
|
|
|
if (isCompanyId) {
|
|
// customerId是公司ID
|
|
viewCustomerDetails(customerId, phoneNumber);
|
|
} else {
|
|
// customerId可能是电话号码,将公司ID设为空
|
|
viewCustomerDetails('', customerId);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// 查看跟进详情功能
|
|
function viewFollowUpDetails(followUpId) {
|
|
currentFollowUpId = followUpId;
|
|
const followUp = followUpData[followUpId];
|
|
|
|
// 清空跟进详情弹窗中的所有数据
|
|
document.getElementById('follow-up-variety').textContent = "-";
|
|
document.getElementById('follow-up-quantity').textContent = "-";
|
|
document.getElementById('follow-up-specification').textContent = "-";
|
|
document.getElementById('follow-up-yolk').textContent = "-";
|
|
document.getElementById('follow-up-shell').textContent = "-";
|
|
document.getElementById('follow-up-quality-requirements').textContent = "-";
|
|
document.getElementById('follow-up-acceptable-price').textContent = "-";
|
|
document.getElementById('follow-up-company').textContent = "-";
|
|
document.getElementById('follow-up-region').textContent = "-";
|
|
document.getElementById('follow-up-level').textContent = "-";
|
|
document.getElementById('follow-up-type').textContent = "-";
|
|
document.getElementById('follow-up-contact').textContent = "-";
|
|
document.getElementById('follow-up-phone').textContent = "-";
|
|
document.getElementById('follow-up-wechat').textContent = "-";
|
|
document.getElementById('follow-up-address').textContent = "-";
|
|
|
|
// 如果有跟进数据,填充详情
|
|
if (followUp) {
|
|
document.getElementById('follow-up-variety').textContent = followUp.productName || "-";
|
|
document.getElementById('follow-up-quantity').textContent = followUp.quantity || "-";
|
|
document.getElementById('follow-up-specification').textContent = followUp.specification || "-";
|
|
document.getElementById('follow-up-yolk').textContent = followUp.yolk || "-";
|
|
document.getElementById('follow-up-shell').textContent = followUp.shell || "-";
|
|
document.getElementById('follow-up-quality-requirements').textContent = followUp.qualityRequirements || "-";
|
|
document.getElementById('follow-up-acceptable-price').textContent = followUp.acceptablePrice || "-";
|
|
|
|
// 公司信息和联系人信息与客户详情一致
|
|
const customerId = followUp.customerId;
|
|
if (customerId && customerData[customerId]) {
|
|
const customer = customerData[customerId];
|
|
document.getElementById('follow-up-company').textContent = customer.company || "-";
|
|
document.getElementById('follow-up-region').textContent = customer.region || "-";
|
|
document.getElementById('follow-up-level').textContent = customer.level || "-";
|
|
document.getElementById('follow-up-type').textContent = customer.type || "-";
|
|
document.getElementById('follow-up-contact').textContent = customer.nickName || "-";
|
|
document.getElementById('follow-up-phone').textContent = customer.phoneNumber || "-";
|
|
document.getElementById('follow-up-wechat').textContent = customer.wechat || "-";
|
|
document.getElementById('follow-up-address').textContent = customer.address || "-";
|
|
}
|
|
}
|
|
|
|
document.getElementById('followUpDetailModal').classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
// 渲染自定义详情 - 调整为与基本信息一致的排版
|
|
function renderCustomDetails() {
|
|
const container = document.getElementById('customDetailsContainer');
|
|
container.innerHTML = '';
|
|
|
|
const customer = customerData[currentCustomerId];
|
|
|
|
if (!customer || !customer.customDetails || customer.customDetails.length === 0) {
|
|
container.innerHTML = '<div class="detail-item"><span class="detail-value">暂无附加联系人信息</span></div>';
|
|
return;
|
|
}
|
|
|
|
customer.customDetails.forEach((detail, index) => {
|
|
const detailGroup = document.createElement('div');
|
|
detailGroup.className = 'detail-group';
|
|
detailGroup.innerHTML = `
|
|
<div class="detail-group-title">附加联系人 #${index + 1}</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">姓名:</span>
|
|
<input type="text" class="detail-input" value="${detail.name}" placeholder="联系人姓名">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">电话:</span>
|
|
<input type="text" class="detail-input" value="${detail.phoneNumber}" placeholder="联系电话">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">账户:</span>
|
|
<input type="text" class="detail-input" value="${detail.account || ''}" placeholder="账户名">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">账号:</span>
|
|
<input type="text" class="detail-input" value="${detail.accountNumber || ''}" placeholder="账号">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">开户行:</span>
|
|
<input type="text" class="detail-input" value="${detail.bank || ''}" placeholder="开户行">
|
|
</div>
|
|
<div class="detail-item">
|
|
<span class="detail-label">地址:</span>
|
|
<input type="text" class="detail-input" value="${detail.address || ''}" placeholder="地址">
|
|
</div>
|
|
<button class="remove-detail-btn" data-index="${index}">
|
|
<i class="fas fa-trash-alt"></i> 删除此联系人
|
|
</button>
|
|
`;
|
|
container.appendChild(detailGroup);
|
|
});
|
|
|
|
// 添加删除按钮事件
|
|
document.querySelectorAll('.remove-detail-btn').forEach(btn => {
|
|
btn.addEventListener('click', function () {
|
|
const index = parseInt(this.getAttribute('data-index'));
|
|
removeCustomDetail(index);
|
|
});
|
|
});
|
|
|
|
// 添加输入框变化事件,实时保存
|
|
document.querySelectorAll('.detail-input').forEach((input, index) => {
|
|
input.addEventListener('change', function () {
|
|
const groupIndex = Math.floor(index / 6); // 每个联系人有6个字段
|
|
const fieldIndex = index % 6;
|
|
|
|
if (fieldIndex === 0) {
|
|
customerData[currentCustomerId].customDetails[groupIndex].name = this.value;
|
|
} else if (fieldIndex === 1) {
|
|
customerData[currentCustomerId].customDetails[groupIndex].phoneNumber = this.value;
|
|
} else if (fieldIndex === 2) {
|
|
customerData[currentCustomerId].customDetails[groupIndex].account = this.value;
|
|
} else if (fieldIndex === 3) {
|
|
customerData[currentCustomerId].customDetails[groupIndex].accountNumber = this.value;
|
|
} else if (fieldIndex === 4) {
|
|
customerData[currentCustomerId].customDetails[groupIndex].bank = this.value;
|
|
} else if (fieldIndex === 5) {
|
|
customerData[currentCustomerId].customDetails[groupIndex].address = this.value;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 添加自定义详情
|
|
function addCustomDetail(name, phoneNumber, account, accountNumber, bank, address) {
|
|
if (!currentCustomerId) return;
|
|
|
|
if (!customerData[currentCustomerId]) {
|
|
customerData[currentCustomerId] = { customDetails: [] };
|
|
} else if (!customerData[currentCustomerId].customDetails) {
|
|
customerData[currentCustomerId].customDetails = [];
|
|
}
|
|
|
|
customerData[currentCustomerId].customDetails.push({
|
|
name,
|
|
phoneNumber,
|
|
account,
|
|
accountNumber,
|
|
bank,
|
|
address
|
|
});
|
|
renderCustomDetails();
|
|
}
|
|
|
|
// 添加调试函数
|
|
function debugTimeFilter() {
|
|
console.log('=== 时间筛选调试信息 ===');
|
|
console.log('当前时间筛选:', window.currentTimeFilter);
|
|
|
|
const currentLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'all';
|
|
console.log('当前等级:', currentLevel);
|
|
|
|
// 获取当前显示的数据量
|
|
const visibleRows = document.querySelectorAll(`#${currentLevel === 'all' ? 'all-customers' : currentLevel + '-customers'} tr:not([style*="display: none"])`);
|
|
console.log('当前显示行数:', visibleRows.length);
|
|
|
|
console.log('=== 结束调试 ===');
|
|
}
|
|
|
|
|
|
// 删除自定义详情
|
|
function removeCustomDetail(index) {
|
|
if (!currentCustomerId || !customerData[currentCustomerId] || !customerData[currentCustomerId].customDetails) return;
|
|
|
|
if (customerData[currentCustomerId].customDetails.length > index) {
|
|
customerData[currentCustomerId].customDetails.splice(index, 1);
|
|
renderCustomDetails();
|
|
}
|
|
}
|
|
|
|
const viewButtons = document.querySelectorAll('.view-details');
|
|
const modal = document.getElementById('customerModal');
|
|
const closeModal = document.getElementById('closeModal');
|
|
const addDetailBtn = document.getElementById('addDetailBtn');
|
|
const addDetailModal = document.getElementById('addDetailModal');
|
|
const cancelAddDetail = document.getElementById('cancelAddDetail');
|
|
const confirmAddDetail = document.getElementById('confirmAddDetail');
|
|
|
|
viewButtons.forEach(button => {
|
|
button.addEventListener('click', function (e) {
|
|
e.stopPropagation();
|
|
const companyId = this.getAttribute('data-company-id') || '';
|
|
const phoneNumber = this.getAttribute('data-phone') || '';
|
|
viewCustomerDetails(companyId, phoneNumber);
|
|
});
|
|
});
|
|
|
|
const customerRows = document.querySelectorAll('#customers-table tbody tr');
|
|
customerRows.forEach(row => {
|
|
row.addEventListener('click', function () {
|
|
const customerId = this.getAttribute('data-id');
|
|
// 获取存储在行上的电话号码
|
|
const phoneNumber = this.getAttribute('data-phone') || this.querySelector('td:nth-child(6)')?.textContent || '';
|
|
|
|
if (customerId) {
|
|
// 检查customerId是公司ID还是电话号码
|
|
// 公司ID通常以"CUST-"开头,或者是其他特定格式
|
|
const isCompanyId = customerId.startsWith('CUST-') || customerId.length > 11;
|
|
|
|
if (isCompanyId) {
|
|
// customerId是公司ID
|
|
viewCustomerDetails(customerId, phoneNumber);
|
|
} else {
|
|
// customerId可能是电话号码,将公司ID设为空
|
|
viewCustomerDetails('', customerId);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
const recentCustomerRows = document.querySelectorAll('#recent-customers-table tbody tr');
|
|
recentCustomerRows.forEach(row => {
|
|
row.addEventListener('click', function () {
|
|
const customerId = this.getAttribute('data-id');
|
|
// 获取存储在行上的电话号码
|
|
const phoneNumber = this.getAttribute('data-phone') || this.querySelector('td:nth-child(6)')?.textContent || '';
|
|
|
|
if (customerId) {
|
|
// 检查customerId是公司ID还是电话号码
|
|
// 公司ID通常以"CUST-"开头,或者是其他特定格式
|
|
const isCompanyId = customerId.startsWith('CUST-') || customerId.length > 11;
|
|
|
|
if (isCompanyId) {
|
|
// customerId是公司ID
|
|
viewCustomerDetails(customerId, phoneNumber);
|
|
} else {
|
|
// customerId可能是电话号码,将公司ID设为空
|
|
viewCustomerDetails('', customerId);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// 跟进记录行点击事件
|
|
const followUpRows = document.querySelectorAll('#follow-up-table tbody tr');
|
|
followUpRows.forEach(row => {
|
|
row.addEventListener('click', function (e) {
|
|
// 如果点击的是跟进内容,则显示详情
|
|
if (e.target.classList.contains('follow-up-content')) {
|
|
const followUpId = this.getAttribute('data-id');
|
|
if (followUpId) {
|
|
viewFollowUpDetails(followUpId);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// 跟进内容点击事件
|
|
document.addEventListener('click', function (e) {
|
|
if (e.target.classList.contains('follow-up-content')) {
|
|
const followUpId = e.target.closest('tr').getAttribute('data-id');
|
|
if (followUpId) {
|
|
viewFollowUpDetails(followUpId);
|
|
}
|
|
}
|
|
});
|
|
|
|
closeModal.addEventListener('click', function () {
|
|
modal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
});
|
|
|
|
modal.addEventListener('click', function (e) {
|
|
if (e.target === modal) {
|
|
modal.classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
}
|
|
});
|
|
|
|
// 新增详情按钮事件
|
|
addDetailBtn.addEventListener('click', function () {
|
|
// 清空输入框
|
|
document.getElementById('newContactName').value = '';
|
|
document.getElementById('newContactPhone').value = '';
|
|
document.getElementById('newContactAccount').value = '';
|
|
document.getElementById('newContactAccountNumber').value = '';
|
|
document.getElementById('newContactBank').value = '';
|
|
document.getElementById('newContactAddress').value = '';
|
|
|
|
// 显示新增详情弹窗
|
|
addDetailModal.classList.add('active');
|
|
});
|
|
|
|
// 取消添加详情
|
|
cancelAddDetail.addEventListener('click', function () {
|
|
addDetailModal.classList.remove('active');
|
|
});
|
|
|
|
// 确认添加详情
|
|
confirmAddDetail.addEventListener('click', function () {
|
|
const name = document.getElementById('newContactName').value.trim();
|
|
const phoneNumber = document.getElementById('newContactPhone').value.trim();
|
|
const account = document.getElementById('newContactAccount').value.trim();
|
|
const accountNumber = document.getElementById('newContactAccountNumber').value.trim();
|
|
const bank = document.getElementById('newContactBank').value.trim();
|
|
const address = document.getElementById('newContactAddress').value.trim();
|
|
|
|
if (!name) {
|
|
alert('请输入联系人姓名');
|
|
return;
|
|
}
|
|
|
|
addCustomDetail(name, phoneNumber, account, accountNumber, bank, address);
|
|
addDetailModal.classList.remove('active');
|
|
});
|
|
|
|
// 点击弹窗外部关闭新增详情弹窗
|
|
addDetailModal.addEventListener('click', function (e) {
|
|
if (e.target === addDetailModal) {
|
|
addDetailModal.classList.remove('active');
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// 在页面加载完成后运行测试
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
testPaginationFunctionality();
|
|
});
|
|
// 全局错误处理
|
|
window.addEventListener('error', function (e) {
|
|
console.error('全局错误:', e.error);
|
|
console.error('错误位置:', e.filename, e.lineno, e.colno);
|
|
});
|
|
|
|
|
|
// 在页面加载时初始化事件
|
|
window.addEventListener('DOMContentLoaded', function () {
|
|
if (!checkLoginStatus()) {
|
|
return;
|
|
}
|
|
// 获取登录信息
|
|
getLoginInfo();
|
|
document.getElementById('add-id').value = generateCustomerId();
|
|
// 初始化分页
|
|
initPagination();
|
|
initLevelPagination();
|
|
// 初始化客户数据显示
|
|
updateStatsCards();
|
|
updateRecentCustomers();
|
|
// 初始绑定
|
|
bindTableRowClickEvents();
|
|
|
|
// 定期重新绑定事件(防止动态加载数据后事件丢失)
|
|
setInterval(() => {
|
|
bindTableRowClickEvents();
|
|
}, 5000);
|
|
setTimeout(() => {
|
|
initEditButton();
|
|
initModalEvents();
|
|
}, 100);
|
|
// 为每个存在的客户等级初始化表格
|
|
Object.values(customerData).forEach(customer => {
|
|
updateCustomerTable(customer);
|
|
});
|
|
debugCustomerLevels(customers);
|
|
|
|
// 绑定新增客户按钮事件
|
|
document.getElementById('dashboard-add-customer').addEventListener('click', openAddCustomerModal);
|
|
document.getElementById('customers-add-customer').addEventListener('click', openAddCustomerModal);
|
|
|
|
// 关闭新增客户弹窗
|
|
document.getElementById('closeAddCustomerModal').addEventListener('click', function () {
|
|
document.getElementById('addCustomerModal').classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
});
|
|
|
|
document.getElementById('cancelAddCustomer').addEventListener('click', function () {
|
|
document.getElementById('addCustomerModal').classList.remove('active');
|
|
document.body.style.overflow = 'auto';
|
|
});
|
|
|
|
fetchPublicSeaData();
|
|
initModalEvents(); // 初始化模态框事件
|
|
});
|
|
|
|
// 修改绑定查看详情事件的部分,确保每次打开详情时都重新初始化
|
|
document.addEventListener('click', function (e) {
|
|
if (e.target.classList.contains('view-details')) {
|
|
const companyId = e.target.getAttribute('data-company-id') || '';
|
|
const phoneNumber = e.target.getAttribute('data-phone') || '';
|
|
if (companyId || phoneNumber) {
|
|
e.stopPropagation();
|
|
viewCustomerDetails(companyId, phoneNumber);
|
|
}
|
|
} else if (e.target.classList.contains('follow-up-btn')) {
|
|
// 修复:确保全局的跟进按钮也调用正确的函数
|
|
e.stopPropagation();
|
|
const companyId = e.target.getAttribute('data-company-id') || '';
|
|
const phoneNumber = e.target.getAttribute('data-phone') || '';
|
|
if (companyId || phoneNumber) {
|
|
showFollowUpInterface(companyId, phoneNumber);
|
|
}
|
|
}
|
|
});
|
|
// 确保数据缓存系统在页面加载时正确初始化
|
|
document.addEventListener('DOMContentLoaded', async function() {
|
|
console.log('🚀 初始化客户数据系统');
|
|
|
|
// 1. 先初始化缓存系统 - 修复:确保即使没有CustomerDataCache类也能正常工作
|
|
if (!window.customerCache) {
|
|
console.log('创建简单缓存系统');
|
|
window.customerCache = {
|
|
data: {},
|
|
set: function(key, value) {
|
|
this.data[key] = value;
|
|
console.log('缓存已更新:', key, '数量:', Array.isArray(value) ? value.length : 1);
|
|
},
|
|
get: function(key) {
|
|
return this.data[key] || null;
|
|
},
|
|
clear: function() {
|
|
this.data = {};
|
|
},
|
|
init: async function() {
|
|
// 简单初始化
|
|
console.log('缓存系统初始化完成');
|
|
},
|
|
preloadAllLevels: async function() {
|
|
// 尝试预加载数据
|
|
try {
|
|
const response = await fetch(appendAuthParams(`${API_BASE_URL}/customers/all`));
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (data.success && Array.isArray(data.data)) {
|
|
this.set('all', data.data);
|
|
allCustomersData = data.data;
|
|
console.log('预加载客户数据成功,数量:', data.data.length);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('预加载数据失败:', error);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
try {
|
|
await window.customerCache.init();
|
|
} catch (error) {
|
|
console.error('缓存系统初始化失败:', error);
|
|
}
|
|
|
|
// 2. 初始化UI组件
|
|
try {
|
|
initUserInfoDropdown();
|
|
initAllCustomersPagination();
|
|
initLevelPagination();
|
|
setupEnhancedEventDelegation(); // 关键:确保事件委托优先设置
|
|
setupLevelTabs();
|
|
initAutoRefresh();
|
|
initTimeFilter();
|
|
console.log('UI组件初始化完成');
|
|
} catch (error) {
|
|
console.error('UI组件初始化失败:', error);
|
|
}
|
|
|
|
// 3. 预加载关键数据 - 修复:添加更健壮的数据加载逻辑
|
|
try {
|
|
await window.customerCache.preloadAllLevels();
|
|
|
|
// 获取缓存的数据
|
|
const allCustomers = window.customerCache.get('all') || [];
|
|
console.log('✅ 所有等级数据预加载完成,数量:', allCustomers.length);
|
|
|
|
// 更新全局数据
|
|
if (allCustomers.length > 0) {
|
|
allCustomersData = allCustomers;
|
|
}
|
|
|
|
// 4. 初始渲染当前活跃标签页
|
|
const activeLevel = document.querySelector('.level-tab.active')?.getAttribute('data-level') || 'all';
|
|
refreshCustomerData(activeLevel);
|
|
|
|
// 移除直接调用updateRecentCustomers()
|
|
// 因为已经在refreshCustomerData函数中添加了对refreshRecentCustomers()的调用
|
|
// 这样可以确保最近客户与全部客户、重要客户、普通客户等一起渲染
|
|
|
|
} catch (error) {
|
|
console.error('❌ 数据预加载失败:', error);
|
|
|
|
// 失败时尝试使用备用数据源
|
|
if (window.customers && Array.isArray(window.customers)) {
|
|
console.log('使用备用数据源,数量:', window.customers.length);
|
|
allCustomersData = window.customers;
|
|
window.customerCache.set('all', window.customers);
|
|
updateAllCustomersPagination(window.customers);
|
|
// 移除直接调用updateRecentCustomers()
|
|
// 依赖refreshCustomerData中的refreshRecentCustomers()调用来同步更新
|
|
}
|
|
}
|
|
|
|
// 修复:移除重复的bindTableRowClickEvents调用,使用事件委托
|
|
console.log('初始化完成,依赖事件委托机制处理交互');
|
|
|
|
// 添加调试信息按钮
|
|
setTimeout(addRefreshButtons, 2000);
|
|
});
|
|
|
|
// 绑定行点击事件的功能已在setupEnhancedEventDelegation中实现
|
|
|
|
// 更新客户通知状态的全局函数
|
|
function updateCustomerNotice(customerId, notificationItem) {
|
|
console.log('⚙️ 处理客户通知状态更新,客户ID:', customerId);
|
|
// 发送请求到后端更新客户的notice状态
|
|
fetch(`/supply/pool/customers/${customerId}/notice`, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ notice: 'old' })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
console.log('✅ 更新客户通知状态成功,响应:', data);
|
|
if (data.success) {
|
|
// 从通知列表中移除该项
|
|
if (notificationItem && notificationItem.parentNode) {
|
|
notificationItem.remove();
|
|
}
|
|
|
|
// 更新通知铃铛状态
|
|
const notificationContent = document.getElementById('notificationContent');
|
|
const remainingNotifications = notificationContent.querySelectorAll('.notification-item').length;
|
|
|
|
if (remainingNotifications === 0) {
|
|
notificationContent.innerHTML = '<p style="text-align: center; color: #666;">暂无通知</p>';
|
|
}
|
|
|
|
// 重新获取数据并更新通知铃铛
|
|
if (typeof CustomerDataCache !== 'undefined') {
|
|
CustomerDataCache.refreshPublicSeaLevels();
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('❌ 更新客户通知状态失败:', error);
|
|
});
|
|
}
|
|
|
|
// 添加通知弹窗样式
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
/* 通知弹窗样式 */
|
|
.notification-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
padding: 16px;
|
|
}
|
|
|
|
.notification-item {
|
|
background: linear-gradient(135deg, #fff5f7 0%, #ffffff 100%);
|
|
border: 2px solid #f8bbd0;
|
|
border-radius: 10px;
|
|
padding: 15px;
|
|
box-shadow: 0 2px 8px rgba(232, 106, 146, 0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.notification-item:hover {
|
|
border-color: #e86a92;
|
|
box-shadow: 0 4px 15px rgba(232, 106, 146, 0.2);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* 未读通知样式 */
|
|
.notification-item.new .notification-title {
|
|
color: #ff4d4f;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.notification-item.new .notification-status {
|
|
background-color: #ff4d4f;
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: 10px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.notification-header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
}
|
|
|
|
.notification-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 40px;
|
|
height: 40px;
|
|
background: #fff7e6;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.notification-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.notification-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
color: #e86a92;
|
|
}
|
|
|
|
.notification-meta {
|
|
display: flex;
|
|
gap: 16px;
|
|
margin-bottom: 8px;
|
|
font-size: 14px;
|
|
color: #666666;
|
|
}
|
|
|
|
.notification-message {
|
|
font-size: 14px;
|
|
color: #ff7875;
|
|
margin-bottom: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.notification-footer {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 12px;
|
|
color: #999999;
|
|
}
|
|
|
|
.notification-time {
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.notification-action-btn {
|
|
padding: 6px 16px;
|
|
background: #1890ff;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.notification-action-btn:hover {
|
|
background: #40a9ff;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.notification-empty {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 48px 24px;
|
|
color: #999999;
|
|
background: #fafafa;
|
|
border-radius: 8px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
/* 适配不同屏幕尺寸 */
|
|
@media (max-width: 768px) {
|
|
.notification-meta {
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.notification-footer {
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
align-items: flex-start;
|
|
}
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|