crm/frontend/js/main.js
2026-01-08 19:33:42 +08:00

1083 lines
38 KiB
JavaScript

document.addEventListener('DOMContentLoaded', function () {
// 登录守卫
const token = localStorage.getItem('crmToken');
if (!token && !window.location.pathname.endsWith('login.html')) {
window.location.href = '/static/login.html';
return;
}
// 封装带 Token 的 fetch
async function authenticatedFetch(url, options = {}) {
const token = localStorage.getItem('crmToken');
const headers = {
...options.headers,
'Authorization': `Bearer ${token}`
};
const response = await fetch(url, { ...options, headers });
if (response.status === 401) {
localStorage.removeItem('crmToken');
window.location.href = '/static/login.html';
return;
}
return response;
}
// Navigation
const navItems = document.querySelectorAll('.nav-item');
const customerSection = document.getElementById('customerSection');
const dashboardSection = document.getElementById('dashboardSection');
const pageTitle = document.getElementById('pageTitle');
// Elements
const createCustomerForm = document.getElementById('createCustomerForm');
const importFileForm = document.getElementById('importFileForm');
const customerTableBody = document.getElementById('customerTableBody');
const addCustomerBtn = document.getElementById('addCustomerBtn');
const importBtn = document.getElementById('importBtn');
const createModal = document.getElementById('createModal');
const importModal = document.getElementById('importModal');
const editModal = document.getElementById('editModal');
const editCustomerForm = document.getElementById('editCustomerForm');
const menuToggle = document.getElementById('menuToggle');
const sidebar = document.querySelector('.sidebar');
const customerFilter = document.getElementById('customerFilter');
const customerSearchInput = document.getElementById('customerSearchInput');
const contentArea = document.querySelector('.content-area');
const refreshCustomersBtn = document.getElementById('refreshCustomersBtn');
const exportCustomersBtn = document.getElementById('exportCustomersBtn');
let allCustomers = [];
let filteredCustomers = [];
let dashboardCustomers = [];
// Chart instances
let statusChartInstance = null;
let typeChartInstance = null;
// Current section tracking
let currentSection = 'customer';
// Pagination state
let currentPage = 1;
let pageSize = 10;
let totalPages = 1;
let totalItems = 0;
let selectedCustomerFilter = '';
let customerSearchQuery = '';
let cellTooltipEl = null;
let tooltipAnchorCell = null;
let tooltipHideTimer = null;
function ensureCellTooltip() {
if (cellTooltipEl) return cellTooltipEl;
cellTooltipEl = document.createElement('div');
cellTooltipEl.className = 'cell-tooltip';
cellTooltipEl.style.display = 'none';
document.body.appendChild(cellTooltipEl);
cellTooltipEl.addEventListener('mouseenter', () => {
if (tooltipHideTimer) {
clearTimeout(tooltipHideTimer);
tooltipHideTimer = null;
}
});
cellTooltipEl.addEventListener('mouseleave', (e) => {
const nextTarget = e.relatedTarget;
if (tooltipAnchorCell && nextTarget && tooltipAnchorCell.contains(nextTarget)) return;
hideCellTooltip(0);
});
return cellTooltipEl;
}
function hideCellTooltip(delayMs = 120) {
if (tooltipHideTimer) clearTimeout(tooltipHideTimer);
tooltipHideTimer = setTimeout(() => {
if (!cellTooltipEl) return;
cellTooltipEl.style.display = 'none';
cellTooltipEl.textContent = '';
cellTooltipEl.removeAttribute('data-placement');
tooltipAnchorCell = null;
}, delayMs);
}
function positionCellTooltipForCell(cell) {
if (!cellTooltipEl) return;
const rect = cell.getBoundingClientRect();
const viewportPadding = 12;
const offset = 10;
cellTooltipEl.style.left = '0px';
cellTooltipEl.style.top = '0px';
cellTooltipEl.style.visibility = 'hidden';
cellTooltipEl.style.display = 'block';
const tipRect = cellTooltipEl.getBoundingClientRect();
const tipWidth = tipRect.width;
const tipHeight = tipRect.height;
const canShowAbove = rect.top >= tipHeight + offset + viewportPadding;
const placement = canShowAbove ? 'top' : 'bottom';
cellTooltipEl.setAttribute('data-placement', placement);
const leftUnclamped = rect.left + rect.width / 2 - tipWidth / 2;
const left = Math.max(viewportPadding, Math.min(leftUnclamped, window.innerWidth - viewportPadding - tipWidth));
const top = placement === 'top'
? rect.top - tipHeight - offset
: Math.min(rect.bottom + offset, window.innerHeight - viewportPadding - tipHeight);
cellTooltipEl.style.left = `${Math.round(left)}px`;
cellTooltipEl.style.top = `${Math.round(top)}px`;
cellTooltipEl.style.visibility = 'visible';
}
function showCellTooltipForCell(cell) {
const rawText = (cell.getAttribute('data-tooltip') || '').trim();
if (!rawText) return;
const tip = ensureCellTooltip();
tip.textContent = rawText;
tooltipAnchorCell = cell;
if (tooltipHideTimer) {
clearTimeout(tooltipHideTimer);
tooltipHideTimer = null;
}
positionCellTooltipForCell(cell);
}
function bindCellTooltipEvents() {
customerTableBody.addEventListener('mouseover', (e) => {
const cell = e.target && e.target.closest ? e.target.closest('td.has-overflow') : null;
if (!cell || !customerTableBody.contains(cell)) return;
if (tooltipAnchorCell === cell && cellTooltipEl && cellTooltipEl.style.display === 'block') return;
showCellTooltipForCell(cell);
});
customerTableBody.addEventListener('mouseout', (e) => {
const fromCell = e.target && e.target.closest ? e.target.closest('td.has-overflow') : null;
if (!fromCell) return;
const nextTarget = e.relatedTarget;
if (cellTooltipEl && nextTarget && cellTooltipEl.contains(nextTarget)) return;
if (tooltipAnchorCell === fromCell) hideCellTooltip(120);
});
const reposition = () => {
if (!cellTooltipEl || cellTooltipEl.style.display !== 'block' || !tooltipAnchorCell) return;
positionCellTooltipForCell(tooltipAnchorCell);
};
window.addEventListener('resize', reposition);
window.addEventListener('scroll', reposition, true);
if (contentArea) contentArea.addEventListener('scroll', reposition, { passive: true });
}
bindCellTooltipEvents();
function normalizeDateValue(dateValue) {
const raw = String(dateValue || '').trim();
if (!raw) return '';
const cleaned = raw.replace(/\./g, '-').replace(/\//g, '-');
const parts = cleaned.split('-').filter(Boolean);
if (parts.length < 3) return '';
const year = parts[0].padStart(4, '0');
const month = parts[1].padStart(2, '0');
const day = parts[2].padStart(2, '0');
return `${year}-${month}-${day}`;
}
// Navigation event listeners
navItems.forEach(item => {
item.addEventListener('click', function (e) {
e.preventDefault();
const section = this.getAttribute('data-section');
switchSection(section);
});
});
// Menu toggle for mobile
menuToggle.addEventListener('click', function () {
sidebar.classList.toggle('open');
});
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function (e) {
if (window.innerWidth <= 768) {
if (!sidebar.contains(e.target) && !menuToggle.contains(e.target)) {
sidebar.classList.remove('open');
}
}
});
// Add Customer button
addCustomerBtn.addEventListener('click', function () {
createModal.style.display = 'block';
});
// Import button
importBtn.addEventListener('click', function () {
importModal.style.display = 'block';
});
// Close create modal
createModal.querySelector('.close').addEventListener('click', function () {
createModal.style.display = 'none';
});
createModal.querySelector('.cancel-create').addEventListener('click', function () {
createModal.style.display = 'none';
});
// Close import modal
importModal.querySelector('.close').addEventListener('click', function () {
importModal.style.display = 'none';
});
importModal.querySelector('.cancel-import').addEventListener('click', function () {
importModal.style.display = 'none';
});
// Close edit modal
editModal.querySelector('.close').addEventListener('click', function () {
editModal.style.display = 'none';
});
editModal.querySelector('.cancel-edit').addEventListener('click', function () {
editModal.style.display = 'none';
});
// File name display
const importFile = document.getElementById('importFile');
const fileName = document.getElementById('fileName');
importFile.addEventListener('change', function () {
if (this.files.length > 0) {
fileName.textContent = this.files[0].name;
} else {
fileName.textContent = '选择文件...';
}
});
// Customer filter change event
customerFilter.addEventListener('change', function () {
selectedCustomerFilter = this.value;
currentPage = 1;
applyAllCustomerFilters();
});
if (customerSearchInput) {
customerSearchInput.addEventListener('input', function () {
customerSearchQuery = (this.value || '').trim();
if (currentSection === 'customer') {
currentPage = 1;
applyAllCustomerFilters();
}
});
}
// Apply date filter for dashboard
document.getElementById('applyFilters').addEventListener('click', function () {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate);
});
// Chart title change events
document.getElementById('statusChartTitle').addEventListener('input', function () {
applyDateFilter(document.getElementById('startDate').value, document.getElementById('endDate').value);
});
document.getElementById('typeChartTitle').addEventListener('input', function () {
applyDateFilter(document.getElementById('startDate').value, document.getElementById('endDate').value);
});
// Switch between sections
function switchSection(section) {
navItems.forEach(item => {
item.classList.remove('active');
});
if (section === 'customer') {
customerSection.classList.add('active');
dashboardSection.classList.remove('active');
document.querySelector('[data-section="customer"]').classList.add('active');
pageTitle.textContent = '客户管理';
currentSection = 'customer';
loadCustomers();
} else if (section === 'dashboard') {
customerSection.classList.remove('active');
dashboardSection.classList.add('active');
document.querySelector('[data-section="dashboard"]').classList.add('active');
pageTitle.textContent = '数据仪表板';
currentSection = 'dashboard';
loadDashboardData();
}
// Close sidebar on mobile after navigation
if (window.innerWidth <= 768) {
sidebar.classList.remove('open');
}
}
// Load customers from API
async function loadCustomers() {
try {
const response = await authenticatedFetch('/api/customers?page=1&pageSize=1000');
const data = await response.json();
if (data.customers) {
allCustomers = data.customers;
populateCustomerFilter();
applyAllCustomerFilters();
}
} catch (error) {
console.error('Error loading customers:', error);
}
}
// Populate customer filter dropdown
function populateCustomerFilter() {
console.log('Populating filter, allCustomers:', allCustomers);
const uniqueCustomers = [...new Set(allCustomers.map(c => c.customerName).filter(c => c))];
console.log('Unique customers:', uniqueCustomers);
customerFilter.innerHTML = '<option value="">全部客户</option>';
uniqueCustomers.forEach(customer => {
const option = document.createElement('option');
option.value = customer;
option.textContent = customer;
customerFilter.appendChild(option);
});
if (selectedCustomerFilter) {
customerFilter.value = selectedCustomerFilter;
}
}
// Filter customers by selected customer
function filterCustomers(selectedCustomer) {
selectedCustomerFilter = selectedCustomer;
currentPage = 1;
applyAllCustomerFilters();
}
function customerMatchesQuery(customer, query) {
if (!query) return true;
const fields = [
customer.customerName,
customer.intendedProduct,
customer.version,
customer.description,
customer.solution,
customer.type,
customer.module,
customer.statusProgress,
customer.reporter
];
const haystack = fields
.map(v => String(v || ''))
.join(' ')
.toLowerCase();
const normalizedHaystack = haystack.replace(/[\/.]/g, '-');
const needle = query.toLowerCase();
const normalizedNeedle = needle.replace(/[\/.]/g, '-');
return normalizedHaystack.includes(normalizedNeedle);
}
function applyAllCustomerFilters() {
let next = [...allCustomers];
if (selectedCustomerFilter) {
next = next.filter(c => c.customerName === selectedCustomerFilter);
}
if (customerSearchQuery) {
next = next.filter(c => customerMatchesQuery(c, customerSearchQuery));
}
filteredCustomers = next;
applyCustomerFilter();
}
function toCsvCell(value) {
const raw = String(value ?? '');
const normalized = raw.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
const escaped = normalized.replace(/"/g, '""');
return `"${escaped}"`;
}
function downloadCsv(filename, rows) {
const content = ['\uFEFF' + rows[0], ...rows.slice(1)].join('\r\n');
const blob = new Blob([content], { type: 'text/csv;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
function exportCustomersToCsv(customers) {
const header = [
'客户',
'版本',
'描述',
'解决方案',
'类型',
'模块',
'状态与进度',
'报告人',
'时间'
].map(toCsvCell).join(',');
const lines = customers.map(c => {
const date = normalizeDateValue(c.intendedProduct) || (c.intendedProduct || '');
const cells = [
c.customerName || '',
c.version || '',
c.description || '',
c.solution || '',
c.type || '',
c.module || '',
c.statusProgress || '',
c.reporter || '',
date
];
return cells.map(toCsvCell).join(',');
});
const now = new Date();
const y = now.getFullYear();
const m = String(now.getMonth() + 1).padStart(2, '0');
const d = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const mm = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
const filename = `客户列表_${y}${m}${d}_${hh}${mm}${ss}.csv`;
downloadCsv(filename, [header, ...lines]);
}
if (refreshCustomersBtn) {
refreshCustomersBtn.addEventListener('click', () => {
if (currentSection === 'customer') {
loadCustomers();
}
});
}
if (exportCustomersBtn) {
exportCustomersBtn.addEventListener('click', () => {
if (currentSection !== 'customer') return;
exportCustomersToCsv(filteredCustomers);
});
}
// Apply customer filter and render table with pagination
function applyCustomerFilter() {
totalItems = filteredCustomers.length;
totalPages = Math.ceil(totalItems / pageSize);
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
const paginatedCustomers = filteredCustomers.slice(startIndex, endIndex);
renderCustomerTable(paginatedCustomers);
updatePaginationControls();
}
// Load all customers for dashboard
async function loadAllCustomers() {
console.log('loadAllCustomers called');
try {
const response = await authenticatedFetch('/api/customers?page=1&pageSize=1000');
const data = await response.json();
console.log('Dashboard data received:', data);
if (data.customers) {
dashboardCustomers = data.customers;
console.log('Dashboard customers set:', dashboardCustomers.length, 'customers');
updateDashboardStats(dashboardCustomers);
renderStatusChart(dashboardCustomers);
renderTypeChart(dashboardCustomers);
} else {
console.error('No customers in dashboard data');
}
} catch (error) {
console.error('Error loading all customers:', error);
}
}
// Apply date filter for dashboard
function applyDateFilter(startDate, endDate) {
let filteredData = dashboardCustomers;
if (startDate) {
filteredData = filteredData.filter(c => {
if (!c.intendedProduct) return false;
const date = normalizeDateValue(c.intendedProduct);
return date && date >= startDate;
});
}
if (endDate) {
filteredData = filteredData.filter(c => {
if (!c.intendedProduct) return false;
const date = normalizeDateValue(c.intendedProduct);
return date && date <= endDate;
});
}
updateDashboardStats(filteredData);
renderStatusChart(filteredData);
renderTypeChart(filteredData);
}
// Render customer table
function renderCustomerTable(customers) {
customerTableBody.innerHTML = '';
customers.forEach(customer => {
const row = document.createElement('tr');
const date = customer.intendedProduct || '';
const fields = [
{ value: customer.customerName || '', name: 'customerName' },
{ value: customer.version || '', name: 'version' },
{ value: customer.description || '', name: 'description' },
{ value: customer.solution || '', name: 'solution' },
{ value: customer.type || '', name: 'type' },
{ value: customer.module || '', name: 'module' },
{ value: customer.statusProgress || '', name: 'statusProgress' },
{ value: customer.reporter || '', name: 'reporter' },
{ value: date, name: 'date' }
];
fields.forEach(field => {
const td = document.createElement('td');
const textValue = String(field.value ?? '');
td.textContent = textValue;
td.setAttribute('data-tooltip', textValue);
if (field.name === 'description' || field.name === 'solution') {
td.classList.add('overflow-cell');
}
row.appendChild(td);
});
const actionTd = document.createElement('td');
actionTd.innerHTML = `
<button class="action-btn edit-btn" data-id="${customer.id}">
<i class="fas fa-edit"></i>
</button>
<button class="action-btn delete-btn" data-id="${customer.id}">
<i class="fas fa-trash"></i>
</button>
`;
row.appendChild(actionTd);
customerTableBody.appendChild(row);
});
checkTextOverflow();
document.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', function () {
const customerId = this.getAttribute('data-id');
openEditModal(customerId);
});
});
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function () {
const customerId = this.getAttribute('data-id');
deleteCustomer(customerId);
});
});
}
function checkTextOverflow() {
// Use requestAnimationFrame to ensure DOM is fully rendered before checking overflow
requestAnimationFrame(() => {
const cells = customerTableBody.querySelectorAll('td[data-tooltip]');
cells.forEach(cell => {
const cellText = (cell.getAttribute('data-tooltip') || '').trim();
const shouldAlwaysShowTooltip = cell.classList.contains('overflow-cell') && cellText.length > 0;
const hasOverflow = cell.scrollWidth > cell.clientWidth;
if (shouldAlwaysShowTooltip || hasOverflow) {
cell.classList.add('has-overflow');
} else {
cell.classList.remove('has-overflow');
}
});
});
}
// Update pagination controls
function updatePaginationControls() {
const startItem = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1;
const endItem = Math.min(currentPage * pageSize, totalItems);
document.getElementById('paginationInfo').textContent =
`显示 ${startItem}-${endItem}${totalItems}`;
document.getElementById('firstPage').disabled = currentPage === 1;
document.getElementById('prevPage').disabled = currentPage === 1;
document.getElementById('nextPage').disabled = currentPage === totalPages;
document.getElementById('lastPage').disabled = currentPage === totalPages;
const pageNumbers = document.getElementById('pageNumbers');
pageNumbers.innerHTML = '';
const maxVisiblePages = 5;
let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
if (endPage - startPage + 1 < maxVisiblePages) {
startPage = Math.max(1, endPage - maxVisiblePages + 1);
}
for (let i = startPage; i <= endPage; i++) {
const pageBtn = document.createElement('button');
pageBtn.className = `page-number ${i === currentPage ? 'active' : ''}`;
pageBtn.textContent = i;
pageBtn.addEventListener('click', () => {
currentPage = i;
loadCustomers();
});
pageNumbers.appendChild(pageBtn);
}
}
// Create customer
createCustomerForm.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = {
customerName: document.getElementById('createCustomerName').value,
intendedProduct: document.getElementById('createIntendedProduct').value,
version: document.getElementById('createVersion').value,
description: document.getElementById('createDescription').value,
solution: document.getElementById('createSolution').value,
type: document.getElementById('createType').value,
module: document.getElementById('createModule').value,
statusProgress: document.getElementById('createStatusProgress').value,
reporter: document.getElementById('createReporter').value
};
try {
const response = await authenticatedFetch('/api/customers', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (response.ok) {
createCustomerForm.reset();
createModal.style.display = 'none';
loadCustomers();
alert('客户创建成功!');
} else {
alert('创建客户时出错');
}
} catch (error) {
console.error('Error creating customer:', error);
alert('创建客户时出错');
}
});
// Import customers
importFileForm.addEventListener('submit', async function (e) {
e.preventDefault();
const formData = new FormData();
const fileInput = document.getElementById('importFile');
if (!fileInput.files.length) {
alert('请选择要导入的文件');
return;
}
formData.append('file', fileInput.files[0]);
try {
const response = await authenticatedFetch('/api/customers/import', {
method: 'POST',
body: formData
});
if (response.ok) {
const result = await response.json();
const message = `客户导入成功!\n导入数量: ${result.importedCount}\n重复数量: ${result.duplicateCount}`;
alert(message);
importFileForm.reset();
fileName.textContent = '选择文件...';
importModal.style.display = 'none';
loadCustomers();
} else {
alert('导入客户时出错');
}
} catch (error) {
console.error('Error importing customers:', error);
alert('导入客户时出错');
}
});
// Open edit modal
async function openEditModal(customerId) {
try {
const response = await authenticatedFetch(`/api/customers/${customerId}`);
const customer = await response.json();
document.getElementById('editCustomerId').value = customer.id;
document.getElementById('editCustomerName').value = customer.customerName || '';
document.getElementById('editIntendedProduct').value = normalizeDateValue(customer.intendedProduct);
document.getElementById('editVersion').value = customer.version || '';
document.getElementById('editDescription').value = customer.description || '';
document.getElementById('editSolution').value = customer.solution || '';
document.getElementById('editType').value = customer.type || '';
document.getElementById('editModule').value = customer.module || '';
document.getElementById('editStatusProgress').value = customer.statusProgress || '';
document.getElementById('editReporter').value = customer.reporter || '';
editModal.style.display = 'block';
} catch (error) {
console.error('Error loading customer for edit:', error);
}
}
window.addEventListener('click', function (e) {
if (e.target === createModal) {
createModal.style.display = 'none';
}
if (e.target === importModal) {
importModal.style.display = 'none';
}
if (e.target === editModal) {
editModal.style.display = 'none';
}
});
// Update customer
editCustomerForm.addEventListener('submit', async function (e) {
e.preventDefault();
const customerId = document.getElementById('editCustomerId').value;
const formData = {
customerName: document.getElementById('editCustomerName').value,
intendedProduct: document.getElementById('editIntendedProduct').value,
version: document.getElementById('editVersion').value,
description: document.getElementById('editDescription').value,
solution: document.getElementById('editSolution').value,
type: document.getElementById('editType').value,
module: document.getElementById('editModule').value,
statusProgress: document.getElementById('editStatusProgress').value,
reporter: document.getElementById('editReporter').value
};
try {
const response = await authenticatedFetch(`/api/customers/${customerId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (response.ok) {
editModal.style.display = 'none';
loadCustomers();
alert('客户更新成功!');
} else {
alert('更新客户时出错');
}
} catch (error) {
console.error('Error updating customer:', error);
alert('更新客户时出错');
}
});
// Delete customer
async function deleteCustomer(customerId) {
if (!confirm('确定要删除这个客户吗?')) {
return;
}
try {
const response = await authenticatedFetch(`/api/customers/${customerId}`, {
method: 'DELETE'
});
if (response.ok) {
loadCustomers();
alert('客户删除成功!');
} else {
alert('删除客户时出错');
}
} catch (error) {
console.error('Error deleting customer:', error);
alert('删除客户时出错');
}
}
// Dashboard functionality
async function loadDashboardData() {
console.log('loadDashboardData called');
await loadAllCustomers();
}
// Update dashboard statistics
function updateDashboardStats(customers) {
const totalCustomers = new Set(customers.map(c => c.customerName).filter(c => c)).size;
const now = new Date();
const currentMonth = String(now.getMonth() + 1).padStart(2, '0');
const currentYear = now.getFullYear();
const newCustomers = new Set(
customers.filter(customer => {
if (!customer.customerName) return false;
const normalized = normalizeDateValue(customer.intendedProduct);
if (!normalized) return false;
const year = parseInt(normalized.slice(0, 4), 10);
const month = normalized.slice(5, 7);
return year === currentYear && month === currentMonth;
}).map(c => c.customerName).filter(c => c)
).size;
const products = new Set(customers.map(c => c.version)).size;
const completed = new Set(
customers.filter(c =>
c.statusProgress && (c.statusProgress.includes('已修复') || c.statusProgress.includes('完成') || c.statusProgress.toLowerCase().includes('complete'))
).map(c => c.customerName).filter(c => c)
).size;
document.getElementById('totalCustomers').textContent = totalCustomers;
document.getElementById('newCustomers').textContent = newCustomers;
document.getElementById('totalProducts').textContent = products;
document.getElementById('completedTasks').textContent = completed;
}
function renderStatusChart(customers) {
const ctx = document.getElementById('statusChart').getContext('2d');
const selectedField = document.getElementById('chartFieldSelect').value;
const chartTitle = document.getElementById('statusChartTitle').value || '数据分布';
const fieldCount = {};
customers.forEach(customer => {
const value = customer[selectedField] || '未设置';
fieldCount[value] = (fieldCount[value] || 0) + 1;
});
const labels = Object.keys(fieldCount);
const data = Object.values(fieldCount);
if (statusChartInstance) {
statusChartInstance.destroy();
}
statusChartInstance = new Chart(ctx, {
type: 'pie',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: [
'#FF6B35',
'#F28C28',
'#333',
'#4CAF50',
'#2196F3',
'#FFC107',
'#9C27B0',
'#00BCD4',
'#8BC34A',
'#FF5722'
]
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
},
title: {
display: true,
text: chartTitle,
font: {
size: 16,
weight: 'bold'
}
}
}
}
});
}
// Render customer type chart
function renderTypeChart(customers) {
console.log('renderTypeChart called with customers:', customers);
const canvas = document.getElementById('typeChart');
console.log('typeChart canvas element:', canvas);
if (!canvas) {
console.error('typeChart canvas not found');
return;
}
const ctx = canvas.getContext('2d');
const chartTitle = document.getElementById('typeChartTitle').value || '客户类型';
if (typeChartInstance) {
typeChartInstance.destroy();
}
const selectedField = document.getElementById('typeChartFieldSelect').value;
const typeCount = {};
customers.forEach(customer => {
const type = customer[selectedField] || '未设置';
typeCount[type] = (typeCount[type] || 0) + 1;
});
const labels = Object.keys(typeCount);
const data = Object.values(typeCount);
console.log('Type chart labels:', labels);
console.log('Type chart data:', data);
typeChartInstance = new Chart(ctx, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: [
'#333',
'#FF6B35',
'#F28C28',
'#4CAF50',
'#2196F3',
'#FFC107'
]
}]
},
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
},
title: {
display: true,
text: chartTitle,
font: {
size: 16,
weight: 'bold'
}
}
}
}
});
console.log('Type chart created successfully');
}
// Initialize the app
loadCustomers().then(() => {
loadAllCustomers();
});
// Pagination event listeners
document.getElementById('firstPage').addEventListener('click', () => {
if (currentPage > 1) {
currentPage = 1;
applyCustomerFilter();
}
});
document.getElementById('prevPage').addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
applyCustomerFilter();
}
});
document.getElementById('nextPage').addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage++;
applyCustomerFilter();
}
});
document.getElementById('lastPage').addEventListener('click', () => {
if (currentPage < totalPages) {
currentPage = totalPages;
applyCustomerFilter();
}
});
document.getElementById('pageSizeSelect').addEventListener('change', (e) => {
pageSize = parseInt(e.target.value);
currentPage = 1;
loadCustomers();
});
// Chart field select event listener
document.getElementById('chartFieldSelect').addEventListener('change', function () {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate);
});
// Type chart field select event listener
document.getElementById('typeChartFieldSelect').addEventListener('change', function () {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate);
});
// 登出功能
const logoutBtn = document.createElement('button');
logoutBtn.className = 'icon-btn';
logoutBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
logoutBtn.title = '登出';
logoutBtn.addEventListener('click', () => {
if (confirm('确定要退出登录吗?')) {
localStorage.removeItem('crmToken');
window.location.href = '/static/login.html';
}
});
const headerRight = document.querySelector('.header-right');
if (headerRight) {
headerRight.appendChild(logoutBtn);
}
});