1567 lines
44 KiB
HTML
1567 lines
44 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>客户管理系统 - 组长管理</title>
|
|
<style>
|
|
/* ========== 统一主题配色 ========== */
|
|
:root {
|
|
--primary: #5bbf93;
|
|
--primary-light: #8fd6b3;
|
|
--primary-dark: #2f8f67;
|
|
--primary-bg: #f0f9f5;
|
|
--primary-gradient: linear-gradient(135deg, #5bbf93 0%, #2f8f67 100%);
|
|
|
|
--text-primary: #2c3e50;
|
|
--text-secondary: #546e7a;
|
|
--text-muted: #90a4ae;
|
|
|
|
--bg-body: #f8fafc;
|
|
--bg-card: #ffffff;
|
|
--bg-hover: #f9fafc;
|
|
|
|
--border: #e2e8f0;
|
|
--border-light: #f1f5f9;
|
|
|
|
--success: #10b981;
|
|
--warning: #f59e0b;
|
|
--danger: #ef4444;
|
|
--info: #3b82f6;
|
|
|
|
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
--shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
--shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
|
|
--radius-sm: 6px;
|
|
--radius: 10px;
|
|
--radius-md: 12px;
|
|
--radius-lg: 16px;
|
|
--radius-full: 9999px;
|
|
|
|
--transition: all 0.2s ease-in-out;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', 'Segoe UI', 'Microsoft YaHei', sans-serif;
|
|
background-color: var(--bg-body);
|
|
color: var(--text-primary);
|
|
line-height: 1.6;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
.admin-container {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
/* ========== 侧边栏 ========== */
|
|
.sidebar {
|
|
width: 280px;
|
|
background: linear-gradient(180deg, #3b82f6 0%, #1d4ed8 100%);
|
|
color: white;
|
|
padding: 30px 0;
|
|
position: fixed;
|
|
height: 100vh;
|
|
overflow-y: auto;
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.logo {
|
|
padding: 0 25px 30px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
.logo h1 {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.logo .subtitle {
|
|
color: rgba(255, 255, 255, 0.6);
|
|
font-size: 0.9rem;
|
|
margin-top: 5px;
|
|
margin-left: 40px;
|
|
}
|
|
|
|
.user-info {
|
|
padding: 0 25px 25px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
.user-avatar {
|
|
width: 40px;
|
|
height: 40px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: bold;
|
|
font-size: 1.2rem;
|
|
backdrop-filter: blur(5px);
|
|
}
|
|
|
|
.nav-menu {
|
|
padding: 0 15px;
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 14px 20px;
|
|
border-radius: var(--radius);
|
|
margin-bottom: 8px;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
color: rgba(255, 255, 255, 0.8);
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: white;
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
color: white;
|
|
border-color: rgba(255, 255, 255, 0.3);
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* ========== 主内容区 ========== */
|
|
.main-content {
|
|
flex: 1;
|
|
margin-left: 280px;
|
|
padding: 30px;
|
|
background-color: var(--bg-body);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.header {
|
|
background: var(--bg-card);
|
|
padding: 24px 30px;
|
|
border-radius: var(--radius-lg);
|
|
margin-bottom: 30px;
|
|
box-shadow: var(--shadow);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.header h2 {
|
|
font-size: 1.8rem;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.header h2::before {
|
|
content: '';
|
|
display: block;
|
|
width: 4px;
|
|
height: 24px;
|
|
background: var(--info);
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
.header .date {
|
|
color: var(--text-muted);
|
|
margin-top: 8px;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
/* ========== 员工管理 ========== */
|
|
.staff-management {
|
|
background: var(--bg-card);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
overflow: hidden;
|
|
margin-bottom: 30px;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.management-header {
|
|
padding: 22px 28px;
|
|
border-bottom: 1px solid var(--border-light);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
background: linear-gradient(90deg, #eff6ff 0%, transparent 100%);
|
|
}
|
|
|
|
.management-header h3 {
|
|
font-size: 1.3rem;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 10px 22px;
|
|
border: none;
|
|
border-radius: var(--radius);
|
|
font-weight: 600;
|
|
font-size: 0.95rem;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--info);
|
|
color: white;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #2563eb;
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.btn-danger {
|
|
background: var(--danger);
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background: #dc2626;
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.btn-warning {
|
|
background: var(--warning);
|
|
color: white;
|
|
}
|
|
|
|
.btn-warning:hover {
|
|
background: #d97706;
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.btn-small {
|
|
padding: 6px 14px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.btn-cancel {
|
|
background: var(--bg-hover);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.btn-cancel:hover {
|
|
background: #e2e8f0;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.btn[disabled] {
|
|
opacity: .7;
|
|
cursor: not-allowed;
|
|
transform: none !important;
|
|
}
|
|
|
|
/* ========== 员工列表 ========== */
|
|
.staff-management-list {
|
|
padding: 0;
|
|
}
|
|
|
|
.management-staff-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20px 28px;
|
|
border-bottom: 1px solid var(--border-light);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.management-staff-item:hover {
|
|
background: var(--bg-hover);
|
|
}
|
|
|
|
.management-staff-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.staff-avatar {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: 50%;
|
|
background: var(--info);
|
|
color: white;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: bold;
|
|
font-size: 1.3rem;
|
|
margin-right: 18px;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.staff-info {
|
|
flex: 1;
|
|
}
|
|
|
|
.staff-name {
|
|
font-weight: 600;
|
|
font-size: 1.1rem;
|
|
margin-bottom: 4px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.staff-details {
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.staff-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-left: auto;
|
|
}
|
|
|
|
/* ========== 模态框 ========== */
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 1000;
|
|
backdrop-filter: blur(3px);
|
|
}
|
|
|
|
.modal {
|
|
background: var(--bg-card);
|
|
border-radius: var(--radius-lg);
|
|
padding: 32px;
|
|
width: 90%;
|
|
max-width: 500px;
|
|
max-height: 80vh;
|
|
overflow-y: auto;
|
|
box-shadow: var(--shadow-lg);
|
|
animation: modalAppear 0.3s ease;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
@keyframes modalAppear {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-20px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.modal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 28px;
|
|
padding-bottom: 20px;
|
|
border-bottom: 1px solid var(--border-light);
|
|
}
|
|
|
|
.modal-header h3 {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.modal-header h3::before {
|
|
content: '';
|
|
display: block;
|
|
width: 4px;
|
|
height: 24px;
|
|
background: var(--info);
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
.modal-close {
|
|
background: none;
|
|
border: none;
|
|
font-size: 1.5rem;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
padding: 8px;
|
|
border-radius: var(--radius);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.modal-close:hover {
|
|
background: var(--bg-hover);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
/* ========== 表单 ========== */
|
|
.form-group {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
margin-bottom: 10px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
font-size: 0.95rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.form-group input,
|
|
.form-group select {
|
|
width: 100%;
|
|
padding: 14px 16px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
font-size: 1rem;
|
|
background: white;
|
|
color: var(--text-primary);
|
|
transition: var(--transition);
|
|
}
|
|
|
|
.form-group input:focus,
|
|
.form-group select:focus {
|
|
outline: none;
|
|
border-color: var(--info);
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
|
|
}
|
|
|
|
.modal-actions {
|
|
display: flex;
|
|
gap: 16px;
|
|
justify-content: flex-end;
|
|
margin-top: 32px;
|
|
}
|
|
|
|
/* ========== 空数据提示 ========== */
|
|
.empty-data {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.empty-data .icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 20px;
|
|
opacity: 0.3;
|
|
}
|
|
|
|
/* ========== 消息提示 ========== */
|
|
.message {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
padding: 16px 24px;
|
|
border-radius: var(--radius);
|
|
font-weight: 600;
|
|
z-index: 10000;
|
|
color: white;
|
|
box-shadow: var(--shadow-lg);
|
|
animation: slideIn 0.3s ease;
|
|
max-width: 400px;
|
|
backdrop-filter: blur(10px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.message.success {
|
|
background: linear-gradient(135deg, var(--success) 0%, #059669 100%);
|
|
}
|
|
|
|
.message.error {
|
|
background: linear-gradient(135deg, var(--danger) 0%, #dc2626 100%);
|
|
}
|
|
|
|
@keyframes slideIn {
|
|
from {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
|
|
to {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes slideOut {
|
|
from {
|
|
transform: translateX(0);
|
|
opacity: 1;
|
|
}
|
|
|
|
to {
|
|
transform: translateX(100%);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.loader {
|
|
display: inline-block;
|
|
width: 18px;
|
|
height: 18px;
|
|
border: 3px solid rgba(255, 255, 255, 0.35);
|
|
border-radius: 50%;
|
|
border-top-color: white;
|
|
animation: spin 1s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
/* ========== 响应式 ========== */
|
|
@media (max-width: 768px) {
|
|
.sidebar {
|
|
width: 70px;
|
|
}
|
|
|
|
.main-content {
|
|
margin-left: 70px;
|
|
padding: 20px;
|
|
}
|
|
|
|
.logo h1,
|
|
.subtitle,
|
|
.user-name,
|
|
.nav-text {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* ========== 滚动条 ========== */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: #f1f5f9;
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: #cbd5e1;
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #94a3b8;
|
|
}
|
|
|
|
/* ========== 数据总览卡片 ========== */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(5, 1fr);
|
|
gap: 20px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--bg-card);
|
|
border-radius: var(--radius-lg);
|
|
padding: 24px;
|
|
box-shadow: var(--shadow);
|
|
border: 1px solid var(--border);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.stat-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 4px;
|
|
height: 100%;
|
|
background: var(--info);
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
.stat-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: var(--shadow-lg);
|
|
border-color: var(--info);
|
|
}
|
|
|
|
.stat-card.amount::before {
|
|
background: #10b981;
|
|
}
|
|
|
|
.stat-card.clients::before {
|
|
background: #3b82f6;
|
|
}
|
|
|
|
.stat-card.expiring::before {
|
|
background: #f59e0b;
|
|
}
|
|
|
|
.stat-card.expired::before {
|
|
background: #ef4444;
|
|
}
|
|
|
|
.stat-card.staff::before {
|
|
background: #8b5cf6;
|
|
}
|
|
|
|
.stat-icon {
|
|
width: 56px;
|
|
height: 56px;
|
|
border-radius: var(--radius);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.stat-icon.blue {
|
|
background: #dbeafe;
|
|
color: #3b82f6;
|
|
}
|
|
|
|
.stat-icon.green {
|
|
background: #d1fae5;
|
|
color: #10b981;
|
|
}
|
|
|
|
.stat-icon.yellow {
|
|
background: #fef3c7;
|
|
color: #f59e0b;
|
|
}
|
|
|
|
.stat-icon.red {
|
|
background: #fee2e2;
|
|
color: #ef4444;
|
|
}
|
|
|
|
.stat-icon.purple {
|
|
background: #ede9fe;
|
|
color: #8b5cf6;
|
|
}
|
|
|
|
.stat-info h3 {
|
|
font-size: 1.8rem;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.stat-info p {
|
|
color: var(--text-muted);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.staff-stats {
|
|
display: flex;
|
|
gap: 16px;
|
|
margin-top: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.staff-stat-badge {
|
|
padding: 4px 10px;
|
|
border-radius: 20px;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.staff-stat-badge.clients {
|
|
background: #dbeafe;
|
|
color: #3b82f6;
|
|
}
|
|
|
|
.staff-stat-badge.amount {
|
|
background: #d1fae5;
|
|
color: #059669;
|
|
}
|
|
|
|
.staff-stat-badge.expiring {
|
|
background: #fef3c7;
|
|
color: #d97706;
|
|
}
|
|
|
|
.staff-stat-badge.expired {
|
|
background: #fee2e2;
|
|
color: #dc2626;
|
|
}
|
|
|
|
/* ========== 员工详细统计卡片 ========== */
|
|
.staff-stats-grid {
|
|
display: none;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 16px;
|
|
margin: 0 28px 20px 28px;
|
|
padding: 20px;
|
|
background: #eff6ff;
|
|
border-radius: var(--radius);
|
|
animation: slideDown 0.3s ease;
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
@keyframes slideDown {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.staff-stat-card {
|
|
background: white;
|
|
padding: 18px;
|
|
border-radius: var(--radius);
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
box-shadow: var(--shadow-sm);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.staff-stat-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow);
|
|
border-color: var(--info);
|
|
}
|
|
|
|
.staff-stat-value {
|
|
font-size: 1.6rem;
|
|
font-weight: 700;
|
|
margin-bottom: 6px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.staff-stat-label {
|
|
font-size: 0.85rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.staff-arrow {
|
|
color: var(--text-muted);
|
|
font-size: 1rem;
|
|
transition: transform 0.3s;
|
|
margin-left: auto;
|
|
}
|
|
|
|
/* ========== 客户详情模态框 ========== */
|
|
.modal.wide {
|
|
max-width: 900px;
|
|
width: 95%;
|
|
}
|
|
|
|
.staff-client-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.staff-client-table th,
|
|
.staff-client-table td {
|
|
padding: 12px 10px;
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border-light);
|
|
}
|
|
|
|
.staff-client-table th {
|
|
background: #f8fafc;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.staff-client-table tr:hover {
|
|
background: var(--bg-hover);
|
|
}
|
|
|
|
@media (max-width: 1200px) {
|
|
.stats-grid {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
}
|
|
|
|
.staff-stats-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.stats-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.staff-stats-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="admin-container">
|
|
<!-- 侧边栏 -->
|
|
<div class="sidebar">
|
|
<div class="logo">
|
|
<h1><i class="fas fa-user-tie"></i> 组长后台</h1>
|
|
<div class="subtitle">客户管理系统</div>
|
|
</div>
|
|
|
|
<div class="user-info">
|
|
<div class="user-avatar">组</div>
|
|
<div class="user-name">
|
|
<div style="font-weight:600;">组长</div>
|
|
<div style="font-size:0.85rem; opacity:0.8;">Team Leader</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="nav-menu">
|
|
<div class="nav-item active" data-page="staff">
|
|
<span><i class="fas fa-users"></i></span>
|
|
<span class="nav-text">员工管理</span>
|
|
</div>
|
|
<div class="nav-item" id="logoutBtn">
|
|
<span><i class="fas fa-sign-out-alt"></i></span>
|
|
<span class="nav-text">退出登录</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 主内容区 - 员工管理 -->
|
|
<div class="main-content" id="staffPage">
|
|
<div class="header">
|
|
<h2>员工管理</h2>
|
|
<div class="date">创建和管理我的员工账号</div>
|
|
</div>
|
|
|
|
<!-- 数据总览 -->
|
|
<div class="stats-grid" id="statsGrid">
|
|
<div class="stat-card amount">
|
|
<div class="stat-icon green"><i class="fas fa-money-bill-wave"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="totalAmount">¥ 0</h3>
|
|
<p>总金额</p>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card staff">
|
|
<div class="stat-icon purple"><i class="fas fa-users"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="totalStaff">0</h3>
|
|
<p>员工总数</p>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card clients">
|
|
<div class="stat-icon blue"><i class="fas fa-address-book"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="totalClients">0</h3>
|
|
<p>客户总数</p>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card expiring">
|
|
<div class="stat-icon yellow"><i class="fas fa-clock"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="totalExpiring">0</h3>
|
|
<p>即将到期</p>
|
|
</div>
|
|
</div>
|
|
<div class="stat-card expired">
|
|
<div class="stat-icon red"><i class="fas fa-exclamation-circle"></i></div>
|
|
<div class="stat-info">
|
|
<h3 id="totalExpired">0</h3>
|
|
<p>已到期</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="staff-management">
|
|
<div class="management-header">
|
|
<h3><i class="fas fa-user-plus"></i> 员工列表</h3>
|
|
<div class="action-buttons">
|
|
<button class="btn btn-primary" onclick="openStaffModal()">
|
|
<i class="fas fa-plus"></i> 添加员工
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="staff-management-list" id="staffManagementList">
|
|
<!-- 员工列表动态加载 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 添加员工模态框 -->
|
|
<div id="addStaffModal" class="modal-overlay" style="display:none;">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3><i class="fas fa-user-plus"></i> 添加员工</h3>
|
|
<button class="modal-close" onclick="closeAddStaffModal()">×</button>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-user"></i> 员工姓名:</label>
|
|
<input type="text" id="staffName" placeholder="请输入员工姓名">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-user-circle"></i> 登录账号:</label>
|
|
<input type="text" id="staffUsername" placeholder="设置登录用户名">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-lock"></i> 登录密码:</label>
|
|
<input type="password" id="staffPassword" placeholder="设置登录密码">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-lock"></i> 确认密码:</label>
|
|
<input type="password" id="staffConfirmPassword" placeholder="再次输入密码">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-phone"></i> 手机号码:</label>
|
|
<input type="tel" id="staffPhone" placeholder="员工手机号码">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-envelope"></i> 邮箱:</label>
|
|
<input type="email" id="staffEmail" placeholder="员工邮箱">
|
|
</div>
|
|
|
|
<div class="modal-actions">
|
|
<button class="btn btn-cancel" onclick="closeAddStaffModal()">取消</button>
|
|
<button class="btn btn-primary" onclick="saveStaff()">保存员工</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 重置密码模态框 -->
|
|
<div id="resetPasswordModal" class="modal-overlay" style="display:none;">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3><i class="fas fa-key"></i> 重置密码</h3>
|
|
<button class="modal-close" onclick="closeResetPasswordModal()">×</button>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-user"></i> 员工:</label>
|
|
<div id="resetStaffName" style="font-weight:600;"></div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-lock"></i> 新密码:</label>
|
|
<input type="password" id="resetStaffPassword" placeholder="请输入新密码">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label><i class="fas fa-lock"></i> 确认密码:</label>
|
|
<input type="password" id="resetStaffConfirmPassword" placeholder="请再次输入新密码">
|
|
</div>
|
|
|
|
<div class="modal-actions">
|
|
<button class="btn btn-cancel" onclick="closeResetPasswordModal()">取消</button>
|
|
<button class="btn btn-primary" onclick="confirmResetPassword()">确认重置</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 员工客户详情模态框 -->
|
|
<div id="staffDetailModal" class="modal-overlay" style="display:none;">
|
|
<div class="modal wide">
|
|
<div class="modal-header">
|
|
<h3 id="staffDetailTitle"><i class="fas fa-user"></i> 员工详情</h3>
|
|
<button class="modal-close" onclick="closeStaffDetailModal()">×</button>
|
|
</div>
|
|
<div id="staffDetailContent">
|
|
<!-- 动态加载 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="message" class="message" style="display:none;"></div>
|
|
|
|
<!-- Font Awesome图标 -->
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
|
|
|
<script>
|
|
// =======================
|
|
// 配置
|
|
// =======================
|
|
const API_BASE = window.location.origin;
|
|
const TOKEN_KEY = 'auth_token';
|
|
const USER_KEY = 'auth_user';
|
|
|
|
let currentResetStaffId = null;
|
|
let currentEditStaffId = null;
|
|
let leaderData = { me: null, staff: [] };
|
|
|
|
function $(id) { return document.getElementById(id); }
|
|
|
|
// =======================
|
|
// 消息提示
|
|
// =======================
|
|
function showMessage(text, type) {
|
|
const msg = $('message');
|
|
msg.textContent = text;
|
|
msg.className = 'message ' + type;
|
|
msg.style.display = 'block';
|
|
|
|
setTimeout(() => {
|
|
msg.style.animation = 'slideOut 0.3s ease';
|
|
setTimeout(() => {
|
|
msg.style.display = 'none';
|
|
msg.style.animation = '';
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// =======================
|
|
// 认证
|
|
// =======================
|
|
function getToken() {
|
|
return localStorage.getItem(TOKEN_KEY) || '';
|
|
}
|
|
|
|
function clearAuth() {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
localStorage.removeItem(USER_KEY);
|
|
}
|
|
|
|
function getAuthUser() {
|
|
try { return JSON.parse(localStorage.getItem(USER_KEY) || 'null'); }
|
|
catch { return null; }
|
|
}
|
|
|
|
function setBtnLoading(btn, loading, text = '处理中...') {
|
|
if (!btn) return;
|
|
if (loading) {
|
|
btn.dataset._oldHtml = btn.innerHTML;
|
|
btn.innerHTML = `<div class="loader"></div> <span>${text}</span>`;
|
|
btn.disabled = true;
|
|
} else {
|
|
btn.innerHTML = btn.dataset._oldHtml || btn.innerHTML;
|
|
btn.disabled = false;
|
|
delete btn.dataset._oldHtml;
|
|
}
|
|
}
|
|
|
|
async function apiFetch(path, { method = 'GET', body, headers = {} } = {}) {
|
|
const token = getToken();
|
|
const res = await fetch(API_BASE + path, {
|
|
method,
|
|
headers: {
|
|
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
...headers
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined
|
|
});
|
|
|
|
if (res.status === 401) {
|
|
clearAuth();
|
|
showMessage('登录已过期,请重新登录', 'error');
|
|
setTimeout(() => location.href = 'index.html', 800);
|
|
throw new Error('Unauthorized');
|
|
}
|
|
|
|
const contentType = res.headers.get('content-type') || '';
|
|
if (!contentType.includes('application/json')) {
|
|
if (!res.ok) throw new Error(`请求失败(${res.status})`);
|
|
return res;
|
|
}
|
|
|
|
const data = await res.json().catch(() => ({}));
|
|
if (!res.ok) throw new Error(data.message || `请求失败(${res.status})`);
|
|
return data;
|
|
}
|
|
|
|
// =======================
|
|
// 验证登录
|
|
// =======================
|
|
async function ensureAuthedOrRedirect() {
|
|
const token = getToken();
|
|
if (!token) {
|
|
location.href = 'index.html';
|
|
return null;
|
|
}
|
|
|
|
const me = await apiFetch('/api/auth/me');
|
|
|
|
// 验证是否为 leader 角色
|
|
if (me.user.role !== 'leader') {
|
|
showMessage('无权访问此页面', 'error');
|
|
clearAuth();
|
|
setTimeout(() => location.href = 'index.html', 800);
|
|
return null;
|
|
}
|
|
|
|
localStorage.setItem(USER_KEY, JSON.stringify(me.user));
|
|
return me.user;
|
|
}
|
|
|
|
// =======================
|
|
// 初始化
|
|
// =======================
|
|
async function initPage() {
|
|
leaderData.me = await ensureAuthedOrRedirect();
|
|
if (!leaderData.me) return;
|
|
|
|
applySidebarUser(leaderData.me);
|
|
initNavigation();
|
|
await loadStaffManagementList();
|
|
}
|
|
|
|
function applySidebarUser(user) {
|
|
try {
|
|
const nameEl = document.querySelector('.user-name > div');
|
|
if (nameEl) nameEl.textContent = user.name || user.username || '组长';
|
|
const avatarEl = document.querySelector('.user-avatar');
|
|
if (avatarEl) avatarEl.textContent = (user.name || user.username || '组').charAt(0);
|
|
} catch { }
|
|
}
|
|
|
|
function initNavigation() {
|
|
const logoutBtn = $('logoutBtn');
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener('click', logout);
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// 员工管理
|
|
// =======================
|
|
async function loadStaffManagementList() {
|
|
const el = $('staffManagementList');
|
|
if (!el) return;
|
|
|
|
try {
|
|
const ret = await apiFetch('/api/staff?includeInactive=true');
|
|
const staffList = ret.staff || [];
|
|
leaderData.staff = staffList; // 保存供后续使用
|
|
|
|
// 更新数据总览
|
|
const totalStaff = staffList.filter(s => s.status === 'active').length;
|
|
const totalClients = staffList.reduce((sum, s) => sum + (s.clientsCount || 0), 0);
|
|
const totalAmount = staffList.reduce((sum, s) => sum + (s.totalAmount || 0), 0);
|
|
const totalExpiring = staffList.reduce((sum, s) => sum + (s.expiringCount || 0), 0);
|
|
const totalExpired = staffList.reduce((sum, s) => sum + (s.expiredCount || 0), 0);
|
|
|
|
$('totalAmount').textContent = '¥ ' + totalAmount.toLocaleString();
|
|
$('totalStaff').textContent = totalStaff;
|
|
$('totalClients').textContent = totalClients;
|
|
$('totalExpiring').textContent = totalExpiring;
|
|
$('totalExpired').textContent = totalExpired;
|
|
|
|
el.innerHTML = '';
|
|
if (staffList.length === 0) {
|
|
el.innerHTML = `
|
|
<div class="empty-data">
|
|
<div class="icon"><i class="fas fa-users"></i></div>
|
|
<p>暂无员工数据,点击"添加员工"创建第一位员工</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
staffList.forEach(staff => {
|
|
const safeStaffName = String(staff.name || staff.username || '')
|
|
.replace(/\\/g, '\\\\')
|
|
.replace(/'/g, "\\'");
|
|
const isInactive = staff.status === 'inactive';
|
|
const itemStyle = isInactive ? 'opacity:0.6;background:#fef2f2;' : '';
|
|
const toggleBtnClass = isInactive ? 'btn-primary' : 'btn-danger';
|
|
const toggleBtnIcon = isInactive ? 'fa-check-circle' : 'fa-ban';
|
|
const toggleBtnText = isInactive ? '启用' : '禁用';
|
|
|
|
// 员工主项(可点击展开)
|
|
const staffItem = document.createElement('div');
|
|
staffItem.className = 'management-staff-item';
|
|
staffItem.style.cssText = itemStyle + 'cursor:pointer;';
|
|
staffItem.onclick = () => toggleStaffStats(staff.id);
|
|
staffItem.innerHTML = `
|
|
<div class="staff-avatar" style="background:${isInactive ? '#9ca3af' : '#3b82f6'}">${(staff.name || staff.username || '').charAt(0)}</div>
|
|
<div class="staff-info">
|
|
<div class="staff-name">${staff.name || staff.username}${isInactive ? '<span style="background:#ef4444;color:white;padding:2px 8px;border-radius:4px;font-size:0.75rem;margin-left:8px;">已禁用</span>' : ''}</div>
|
|
<div class="staff-stats">
|
|
<span class="staff-stat-badge amount"><i class="fas fa-yen-sign"></i> ¥${(staff.totalAmount || 0).toLocaleString()}</span>
|
|
<span class="staff-stat-badge clients"><i class="fas fa-address-book"></i> 客户 ${staff.clientsCount || 0}</span>
|
|
${staff.expiringCount ? `<span class="staff-stat-badge expiring"><i class="fas fa-clock"></i> 即将到期 ${staff.expiringCount}</span>` : ''}
|
|
${staff.expiredCount ? `<span class="staff-stat-badge expired"><i class="fas fa-exclamation-circle"></i> 已到期 ${staff.expiredCount}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="staff-arrow" id="arrow-${staff.id}">▶</div>
|
|
<div class="staff-actions" onclick="event.stopPropagation()">
|
|
<button class="btn btn-small btn-primary" onclick='openStaffModal(${JSON.stringify(staff).replace(/'/g, "'")})'>
|
|
<i class="fas fa-edit"></i> 编辑
|
|
</button>
|
|
<button class="btn btn-small btn-warning" onclick="openResetPasswordModal(${staff.id}, '${safeStaffName}')">
|
|
<i class="fas fa-key"></i> 重置密码
|
|
</button>
|
|
<button class="btn btn-small ${toggleBtnClass}" onclick="toggleUserStatus(${staff.id})">
|
|
<i class="fas ${toggleBtnIcon}"></i> ${toggleBtnText}
|
|
</button>
|
|
</div>
|
|
`;
|
|
el.appendChild(staffItem);
|
|
|
|
// 可展开统计卡片
|
|
const statsContainer = document.createElement('div');
|
|
statsContainer.className = 'staff-stats-grid';
|
|
statsContainer.id = `stats-${staff.id}`;
|
|
statsContainer.innerHTML = `
|
|
<div class="staff-stat-card" onclick="showStaffDetail(${staff.id}, 'total')">
|
|
<div class="staff-stat-value">¥ ${(staff.totalAmount || 0).toLocaleString()}</div>
|
|
<div class="staff-stat-label">总金额</div>
|
|
</div>
|
|
<div class="staff-stat-card" onclick="showStaffDetail(${staff.id}, 'clients')">
|
|
<div class="staff-stat-value">${staff.clientsCount || 0}</div>
|
|
<div class="staff-stat-label">客户总数</div>
|
|
</div>
|
|
<div class="staff-stat-card" onclick="showStaffDetail(${staff.id}, 'expiring')">
|
|
<div class="staff-stat-value">${staff.expiringCount || 0}</div>
|
|
<div class="staff-stat-label">即将到期</div>
|
|
</div>
|
|
<div class="staff-stat-card" onclick="showStaffDetail(${staff.id}, 'expired')">
|
|
<div class="staff-stat-value">${staff.expiredCount || 0}</div>
|
|
<div class="staff-stat-label">已到期</div>
|
|
</div>
|
|
`;
|
|
el.appendChild(statsContainer);
|
|
});
|
|
} catch (e) {
|
|
el.innerHTML = `
|
|
<div class="empty-data">
|
|
<div class="icon"><i class="fas fa-exclamation-triangle"></i></div>
|
|
<p>加载失败:${e.message}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// 添加员工
|
|
// =======================
|
|
// =======================
|
|
// 添加/编辑员工
|
|
// =======================
|
|
function openStaffModal(staff = null) {
|
|
const modal = $('addStaffModal');
|
|
const title = modal.querySelector('.modal-header h3');
|
|
modal.style.display = 'flex';
|
|
|
|
// 重置表单
|
|
$('staffName').value = '';
|
|
$('staffUsername').value = '';
|
|
$('staffPassword').value = '';
|
|
$('staffConfirmPassword').value = '';
|
|
$('staffPhone').value = '';
|
|
$('staffEmail').value = '';
|
|
|
|
if (staff) {
|
|
// 编辑模式
|
|
currentEditStaffId = staff.id;
|
|
title.innerHTML = '<i class="fas fa-user-edit"></i> 编辑员工';
|
|
|
|
$('staffName').value = staff.name || '';
|
|
$('staffUsername').value = staff.username || '';
|
|
$('staffPhone').value = staff.phone || '';
|
|
$('staffEmail').value = staff.email || '';
|
|
|
|
$('staffPassword').placeholder = '留空则不修改密码';
|
|
$('staffConfirmPassword').placeholder = '留空则不修改密码';
|
|
} else {
|
|
// 新增模式
|
|
currentEditStaffId = null;
|
|
title.innerHTML = '<i class="fas fa-user-plus"></i> 添加员工';
|
|
|
|
$('staffPassword').placeholder = '设置登录密码';
|
|
$('staffConfirmPassword').placeholder = '再次输入密码';
|
|
}
|
|
}
|
|
|
|
function closeAddStaffModal() {
|
|
$('addStaffModal').style.display = 'none';
|
|
currentEditStaffId = null;
|
|
}
|
|
|
|
async function saveStaff() {
|
|
const btn = document.querySelector('#addStaffModal .btn-primary');
|
|
const name = $('staffName').value.trim();
|
|
const username = $('staffUsername').value.trim();
|
|
const password = $('staffPassword').value;
|
|
const confirmPassword = $('staffConfirmPassword').value;
|
|
const phone = $('staffPhone').value.trim();
|
|
const email = $('staffEmail').value.trim();
|
|
|
|
if (!name || !username) return showMessage('请填写姓名和登录账号', 'error');
|
|
|
|
// 新增时必须填密码,编辑时可选
|
|
if (!currentEditStaffId && !password) return showMessage('请设置登录密码', 'error');
|
|
|
|
if (password && password !== confirmPassword) return showMessage('两次输入的密码不一致', 'error');
|
|
|
|
try {
|
|
setBtnLoading(btn, true, '保存中...');
|
|
|
|
if (currentEditStaffId) {
|
|
// 编辑
|
|
const body = { name, username, phone, email };
|
|
if (password) body.password = password;
|
|
await apiFetch(`/api/staff/${currentEditStaffId}`, { method: 'PUT', body });
|
|
showMessage('员工修改成功', 'success');
|
|
} else {
|
|
// 新增
|
|
await apiFetch('/api/staff', { method: 'POST', body: { name, username, password, phone, email } });
|
|
showMessage('员工添加成功', 'success');
|
|
}
|
|
|
|
closeAddStaffModal();
|
|
await loadStaffManagementList();
|
|
} catch (e) {
|
|
showMessage('添加失败:' + e.message, 'error');
|
|
} finally {
|
|
setBtnLoading(btn, false);
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// 重置密码
|
|
// =======================
|
|
function openResetPasswordModal(staffId, staffName) {
|
|
currentResetStaffId = staffId;
|
|
$('resetStaffName').textContent = staffName || '-';
|
|
$('resetStaffPassword').value = '';
|
|
$('resetStaffConfirmPassword').value = '';
|
|
$('resetPasswordModal').style.display = 'flex';
|
|
}
|
|
|
|
function closeResetPasswordModal() {
|
|
$('resetPasswordModal').style.display = 'none';
|
|
currentResetStaffId = null;
|
|
}
|
|
|
|
async function confirmResetPassword() {
|
|
if (!currentResetStaffId) return showMessage('请选择员工', 'error');
|
|
const btn = document.querySelector('#resetPasswordModal .btn-primary');
|
|
const password = $('resetStaffPassword').value;
|
|
const confirmPassword = $('resetStaffConfirmPassword').value;
|
|
if (!password) return showMessage('请输入新密码', 'error');
|
|
if (password !== confirmPassword) return showMessage('两次输入的密码不一致', 'error');
|
|
|
|
try {
|
|
setBtnLoading(btn, true, '重置中...');
|
|
await apiFetch(`/api/staff/${currentResetStaffId}/reset_password`, {
|
|
method: 'POST',
|
|
body: { password }
|
|
});
|
|
closeResetPasswordModal();
|
|
showMessage('密码已重置', 'success');
|
|
} catch (e) {
|
|
showMessage('重置失败:' + e.message, 'error');
|
|
} finally {
|
|
setBtnLoading(btn, false);
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// 启用/禁用员工
|
|
// =======================
|
|
async function toggleUserStatus(userId) {
|
|
try {
|
|
const ret = await apiFetch(`/api/staff/${userId}/toggle_status`, { method: 'POST' });
|
|
showMessage(ret.status === 'active' ? '已启用' : '已禁用', 'success');
|
|
await loadStaffManagementList();
|
|
} catch (e) {
|
|
showMessage('操作失败:' + e.message, 'error');
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// 展开/收起员工统计
|
|
// =======================
|
|
function toggleStaffStats(staffId) {
|
|
const statsContainer = document.getElementById(`stats-${staffId}`);
|
|
const arrow = document.getElementById(`arrow-${staffId}`);
|
|
if (!statsContainer || !arrow) return;
|
|
|
|
if (statsContainer.style.display === 'grid') {
|
|
statsContainer.style.display = 'none';
|
|
arrow.style.transform = 'rotate(0deg)';
|
|
} else {
|
|
// 关闭其他展开的
|
|
leaderData.staff.forEach(staff => {
|
|
if (staff.id !== staffId) {
|
|
const otherStats = document.getElementById(`stats-${staff.id}`);
|
|
const otherArrow = document.getElementById(`arrow-${staff.id}`);
|
|
if (otherStats) otherStats.style.display = 'none';
|
|
if (otherArrow) otherArrow.style.transform = 'rotate(0deg)';
|
|
}
|
|
});
|
|
statsContainer.style.display = 'grid';
|
|
arrow.style.transform = 'rotate(90deg)';
|
|
}
|
|
}
|
|
|
|
// =======================
|
|
// 查看员工客户详情
|
|
// =======================
|
|
async function showStaffDetail(staffId, type) {
|
|
const staff = leaderData.staff.find(s => s.id === staffId);
|
|
if (!staff) return;
|
|
|
|
let status = 'all';
|
|
if (type === 'expired') status = 'expired';
|
|
if (type === 'expiring') status = 'expiring';
|
|
|
|
try {
|
|
const ret = await apiFetch(`/api/clients?staffId=${staffId}&status=${status}`);
|
|
const clients = ret.clients || [];
|
|
|
|
let title = '', description = '';
|
|
if (type === 'total' || type === 'clients') {
|
|
title = `${staff.name || staff.username} - 所有客户`;
|
|
description = `共 ${clients.length} 个客户,总金额 ¥${(staff.totalAmount || 0).toLocaleString()}`;
|
|
} else if (type === 'expired') {
|
|
title = `${staff.name || staff.username} - 已过期客户`;
|
|
description = `共 ${clients.length} 个已过期客户`;
|
|
} else if (type === 'expiring') {
|
|
title = `${staff.name || staff.username} - 即将到期客户`;
|
|
description = `共 ${clients.length} 个即将到期客户`;
|
|
}
|
|
|
|
$('staffDetailTitle').innerHTML = `<i class="fas fa-user"></i> ${title}`;
|
|
|
|
let content = `
|
|
<div style="margin-bottom: 20px; color: var(--text-muted);">
|
|
<i class="fas fa-info-circle"></i> ${description}
|
|
</div>
|
|
`;
|
|
|
|
if (clients.length === 0) {
|
|
content += `
|
|
<div class="empty-data">
|
|
<div class="icon"><i class="fas fa-database"></i></div>
|
|
<p>暂无相关客户数据</p>
|
|
</div>
|
|
`;
|
|
} else {
|
|
content += `
|
|
<table class="staff-client-table">
|
|
<thead>
|
|
<tr>
|
|
<th>客户姓名</th><th>联系电话</th><th>服务类型</th><th>登记日期</th>
|
|
<th>到期时间</th><th>金额</th><th>状态</th><th>备注</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
clients.forEach(client => {
|
|
const statusInfo = getStatusInfo(client);
|
|
content += `
|
|
<tr>
|
|
<td>${client.customer || '-'}</td>
|
|
<td>${client.phone || '-'}</td>
|
|
<td>${client.service || '-'}</td>
|
|
<td>${client.regDate || '-'}</td>
|
|
<td>${client.expireDate || '-'}</td>
|
|
<td style="font-weight:600;">¥ ${(client.amount || 0).toLocaleString()}</td>
|
|
<td style="${statusInfo.style} font-weight:600;">${statusInfo.text}</td>
|
|
<td>${client.remark || '-'}</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
content += `</tbody></table>`;
|
|
}
|
|
|
|
$('staffDetailContent').innerHTML = content;
|
|
$('staffDetailModal').style.display = 'flex';
|
|
} catch (e) {
|
|
showMessage('加载客户数据失败:' + e.message, 'error');
|
|
}
|
|
}
|
|
|
|
function getStatusInfo(client) {
|
|
if (!client.expireDate) return { text: '未知', style: 'color:#9ca3af;' };
|
|
const now = new Date();
|
|
const exp = new Date(client.expireDate);
|
|
const diffDays = Math.ceil((exp - now) / (1000 * 60 * 60 * 24));
|
|
if (diffDays < 0) return { text: '已过期', style: 'color:#ef4444;' };
|
|
if (diffDays <= 7) return { text: '即将到期', style: 'color:#f59e0b;' };
|
|
return { text: '正常', style: 'color:#10b981;' };
|
|
}
|
|
|
|
function closeStaffDetailModal() {
|
|
$('staffDetailModal').style.display = 'none';
|
|
}
|
|
|
|
// =======================
|
|
// 退出登录
|
|
// =======================
|
|
function logout() {
|
|
if (!confirm('确定要退出登录吗?')) return;
|
|
clearAuth();
|
|
showMessage('已退出登录', 'success');
|
|
setTimeout(() => location.href = 'index.html', 600);
|
|
}
|
|
|
|
// =======================
|
|
// 页面加载
|
|
// =======================
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
initPage().catch(e => {
|
|
if (String(e.message || '').includes('Unauthorized')) return;
|
|
showMessage('初始化失败:' + e.message, 'error');
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|