feat: Add new server binaries and reorder 'Time' column in customer table and CSV export.

This commit is contained in:
hangyu.tao 2026-01-05 20:09:33 +08:00
parent 9e8c8ec8f2
commit 2b50b3a2f7
3 changed files with 193 additions and 179 deletions

View File

@ -107,7 +107,6 @@
<table id="customerTable"> <table id="customerTable">
<thead> <thead>
<tr> <tr>
<th>时间</th>
<th>客户</th> <th>客户</th>
<th>版本</th> <th>版本</th>
<th>描述</th> <th>描述</th>
@ -116,6 +115,7 @@
<th>模块</th> <th>模块</th>
<th>状态与进度</th> <th>状态与进度</th>
<th>报告人</th> <th>报告人</th>
<th>时间</th>
<th>操作</th> <th>操作</th>
</tr> </tr>
</thead> </thead>

View File

@ -1,4 +1,4 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function () {
// 登录守卫 // 登录守卫
const token = localStorage.getItem('crmToken'); const token = localStorage.getItem('crmToken');
if (!token && !window.location.pathname.endsWith('login.html')) { if (!token && !window.location.pathname.endsWith('login.html')) {
@ -201,7 +201,7 @@ document.addEventListener('DOMContentLoaded', function() {
// Navigation event listeners // Navigation event listeners
navItems.forEach(item => { navItems.forEach(item => {
item.addEventListener('click', function(e) { item.addEventListener('click', function (e) {
e.preventDefault(); e.preventDefault();
const section = this.getAttribute('data-section'); const section = this.getAttribute('data-section');
switchSection(section); switchSection(section);
@ -209,12 +209,12 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// Menu toggle for mobile // Menu toggle for mobile
menuToggle.addEventListener('click', function() { menuToggle.addEventListener('click', function () {
sidebar.classList.toggle('open'); sidebar.classList.toggle('open');
}); });
// Close sidebar when clicking outside on mobile // Close sidebar when clicking outside on mobile
document.addEventListener('click', function(e) { document.addEventListener('click', function (e) {
if (window.innerWidth <= 768) { if (window.innerWidth <= 768) {
if (!sidebar.contains(e.target) && !menuToggle.contains(e.target)) { if (!sidebar.contains(e.target) && !menuToggle.contains(e.target)) {
sidebar.classList.remove('open'); sidebar.classList.remove('open');
@ -223,39 +223,39 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// Add Customer button // Add Customer button
addCustomerBtn.addEventListener('click', function() { addCustomerBtn.addEventListener('click', function () {
createModal.style.display = 'block'; createModal.style.display = 'block';
}); });
// Import button // Import button
importBtn.addEventListener('click', function() { importBtn.addEventListener('click', function () {
importModal.style.display = 'block'; importModal.style.display = 'block';
}); });
// Close create modal // Close create modal
createModal.querySelector('.close').addEventListener('click', function() { createModal.querySelector('.close').addEventListener('click', function () {
createModal.style.display = 'none'; createModal.style.display = 'none';
}); });
createModal.querySelector('.cancel-create').addEventListener('click', function() { createModal.querySelector('.cancel-create').addEventListener('click', function () {
createModal.style.display = 'none'; createModal.style.display = 'none';
}); });
// Close import modal // Close import modal
importModal.querySelector('.close').addEventListener('click', function() { importModal.querySelector('.close').addEventListener('click', function () {
importModal.style.display = 'none'; importModal.style.display = 'none';
}); });
importModal.querySelector('.cancel-import').addEventListener('click', function() { importModal.querySelector('.cancel-import').addEventListener('click', function () {
importModal.style.display = 'none'; importModal.style.display = 'none';
}); });
// Close edit modal // Close edit modal
editModal.querySelector('.close').addEventListener('click', function() { editModal.querySelector('.close').addEventListener('click', function () {
editModal.style.display = 'none'; editModal.style.display = 'none';
}); });
editModal.querySelector('.cancel-edit').addEventListener('click', function() { editModal.querySelector('.cancel-edit').addEventListener('click', function () {
editModal.style.display = 'none'; editModal.style.display = 'none';
}); });
@ -263,7 +263,7 @@ document.addEventListener('DOMContentLoaded', function() {
const importFile = document.getElementById('importFile'); const importFile = document.getElementById('importFile');
const fileName = document.getElementById('fileName'); const fileName = document.getElementById('fileName');
importFile.addEventListener('change', function() { importFile.addEventListener('change', function () {
if (this.files.length > 0) { if (this.files.length > 0) {
fileName.textContent = this.files[0].name; fileName.textContent = this.files[0].name;
} else { } else {
@ -272,14 +272,14 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// Customer filter change event // Customer filter change event
customerFilter.addEventListener('change', function() { customerFilter.addEventListener('change', function () {
selectedCustomerFilter = this.value; selectedCustomerFilter = this.value;
currentPage = 1; currentPage = 1;
applyAllCustomerFilters(); applyAllCustomerFilters();
}); });
if (customerSearchInput) { if (customerSearchInput) {
customerSearchInput.addEventListener('input', function() { customerSearchInput.addEventListener('input', function () {
customerSearchQuery = (this.value || '').trim(); customerSearchQuery = (this.value || '').trim();
if (currentSection === 'customer') { if (currentSection === 'customer') {
currentPage = 1; currentPage = 1;
@ -289,18 +289,18 @@ document.addEventListener('DOMContentLoaded', function() {
} }
// Apply date filter for dashboard // Apply date filter for dashboard
document.getElementById('applyFilters').addEventListener('click', function() { document.getElementById('applyFilters').addEventListener('click', function () {
const startDate = document.getElementById('startDate').value; const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value; const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate); applyDateFilter(startDate, endDate);
}); });
// Chart title change events // Chart title change events
document.getElementById('statusChartTitle').addEventListener('input', function() { document.getElementById('statusChartTitle').addEventListener('input', function () {
applyDateFilter(document.getElementById('startDate').value, document.getElementById('endDate').value); applyDateFilter(document.getElementById('startDate').value, document.getElementById('endDate').value);
}); });
document.getElementById('typeChartTitle').addEventListener('input', function() { document.getElementById('typeChartTitle').addEventListener('input', function () {
applyDateFilter(document.getElementById('startDate').value, document.getElementById('endDate').value); applyDateFilter(document.getElementById('startDate').value, document.getElementById('endDate').value);
}); });
@ -438,7 +438,6 @@ document.addEventListener('DOMContentLoaded', function() {
function exportCustomersToCsv(customers) { function exportCustomersToCsv(customers) {
const header = [ const header = [
'时间',
'客户', '客户',
'版本', '版本',
'描述', '描述',
@ -446,13 +445,13 @@ document.addEventListener('DOMContentLoaded', function() {
'类型', '类型',
'模块', '模块',
'状态与进度', '状态与进度',
'报告人' '报告人',
'时间'
].map(toCsvCell).join(','); ].map(toCsvCell).join(',');
const lines = customers.map(c => { const lines = customers.map(c => {
const date = normalizeDateValue(c.customerName) || (c.customerName || ''); const date = normalizeDateValue(c.customerName) || (c.customerName || '');
const cells = [ const cells = [
date,
c.intendedProduct || '', c.intendedProduct || '',
c.version || '', c.version || '',
c.description || '', c.description || '',
@ -460,7 +459,8 @@ document.addEventListener('DOMContentLoaded', function() {
c.type || '', c.type || '',
c.module || '', c.module || '',
c.statusProgress || '', c.statusProgress || '',
c.reporter || '' c.reporter || '',
date
]; ];
return cells.map(toCsvCell).join(','); return cells.map(toCsvCell).join(',');
}); });
@ -563,7 +563,6 @@ document.addEventListener('DOMContentLoaded', function() {
const date = customer.customerName || ''; const date = customer.customerName || '';
const fields = [ const fields = [
{ value: date, name: 'date' },
{ value: customer.intendedProduct || '', name: 'intendedProduct' }, { value: customer.intendedProduct || '', name: 'intendedProduct' },
{ value: customer.version || '', name: 'version' }, { value: customer.version || '', name: 'version' },
{ value: customer.description || '', name: 'description' }, { value: customer.description || '', name: 'description' },
@ -571,7 +570,8 @@ document.addEventListener('DOMContentLoaded', function() {
{ value: customer.type || '', name: 'type' }, { value: customer.type || '', name: 'type' },
{ value: customer.module || '', name: 'module' }, { value: customer.module || '', name: 'module' },
{ value: customer.statusProgress || '', name: 'statusProgress' }, { value: customer.statusProgress || '', name: 'statusProgress' },
{ value: customer.reporter || '', name: 'reporter' } { value: customer.reporter || '', name: 'reporter' },
{ value: date, name: 'date' }
]; ];
fields.forEach(field => { fields.forEach(field => {
@ -604,14 +604,14 @@ document.addEventListener('DOMContentLoaded', function() {
checkTextOverflow(); checkTextOverflow();
document.querySelectorAll('.edit-btn').forEach(btn => { document.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', function() { btn.addEventListener('click', function () {
const customerId = this.getAttribute('data-id'); const customerId = this.getAttribute('data-id');
openEditModal(customerId); openEditModal(customerId);
}); });
}); });
document.querySelectorAll('.delete-btn').forEach(btn => { document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function() { btn.addEventListener('click', function () {
const customerId = this.getAttribute('data-id'); const customerId = this.getAttribute('data-id');
deleteCustomer(customerId); deleteCustomer(customerId);
}); });
@ -673,7 +673,7 @@ document.addEventListener('DOMContentLoaded', function() {
} }
// Create customer // Create customer
createCustomerForm.addEventListener('submit', async function(e) { createCustomerForm.addEventListener('submit', async function (e) {
e.preventDefault(); e.preventDefault();
const formData = { const formData = {
@ -712,7 +712,7 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// Import customers // Import customers
importFileForm.addEventListener('submit', async function(e) { importFileForm.addEventListener('submit', async function (e) {
e.preventDefault(); e.preventDefault();
const formData = new FormData(); const formData = new FormData();
@ -771,7 +771,7 @@ document.addEventListener('DOMContentLoaded', function() {
} }
} }
window.addEventListener('click', function(e) { window.addEventListener('click', function (e) {
if (e.target === createModal) { if (e.target === createModal) {
createModal.style.display = 'none'; createModal.style.display = 'none';
} }
@ -784,7 +784,7 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// Update customer // Update customer
editCustomerForm.addEventListener('submit', async function(e) { editCustomerForm.addEventListener('submit', async function (e) {
e.preventDefault(); e.preventDefault();
const customerId = document.getElementById('editCustomerId').value; const customerId = document.getElementById('editCustomerId').value;
@ -1050,14 +1050,14 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
// Chart field select event listener // Chart field select event listener
document.getElementById('chartFieldSelect').addEventListener('change', function() { document.getElementById('chartFieldSelect').addEventListener('change', function () {
const startDate = document.getElementById('startDate').value; const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value; const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate); applyDateFilter(startDate, endDate);
}); });
// Type chart field select event listener // Type chart field select event listener
document.getElementById('typeChartFieldSelect').addEventListener('change', function() { document.getElementById('typeChartFieldSelect').addEventListener('change', function () {
const startDate = document.getElementById('startDate').value; const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value; const endDate = document.getElementById('endDate').value;
applyDateFilter(startDate, endDate); applyDateFilter(startDate, endDate);

View File

@ -1,14 +1,14 @@
package storage package storage
import ( import (
"crm-go/models"
"crypto/rand" "crypto/rand"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"os" "os"
"sync"
"path/filepath" "path/filepath"
"sync"
"time" "time"
"crm-go/models"
) )
type CustomerStorage interface { type CustomerStorage interface {
@ -43,7 +43,21 @@ func (cs *customerStorage) GetAllCustomers() ([]models.Customer, error) {
cs.mutex.RLock() cs.mutex.RLock()
defer cs.mutex.RUnlock() defer cs.mutex.RUnlock()
return cs.LoadCustomers() customers, err := cs.LoadCustomers()
if err != nil {
return nil, err
}
// Sort by CreatedAt in descending order (newest first)
for i := 0; i < len(customers)-1; i++ {
for j := i + 1; j < len(customers); j++ {
if customers[i].CreatedAt.Before(customers[j].CreatedAt) {
customers[i], customers[j] = customers[j], customers[i]
}
}
}
return customers, nil
} }
func (cs *customerStorage) GetCustomerByID(id string) (*models.Customer, error) { func (cs *customerStorage) GetCustomerByID(id string) (*models.Customer, error) {