feat_ui
This commit is contained in:
parent
ceb0f74ccc
commit
97d5f51ff2
@ -2244,4 +2244,395 @@ tr:hover .action-cell {
|
||||
|
||||
.table-refreshing tbody tr {
|
||||
animation: rowHighlight 0.4s ease-out;
|
||||
}
|
||||
|
||||
/* ==================== Customer Tabs Styles ==================== */
|
||||
.customer-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.customer-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 20px;
|
||||
background: var(--white);
|
||||
color: var(--medium-gray);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.customer-tab:hover {
|
||||
border-color: var(--primary-orange);
|
||||
color: var(--primary-orange);
|
||||
background: rgba(255, 107, 53, 0.05);
|
||||
}
|
||||
|
||||
.customer-tab.active {
|
||||
background: linear-gradient(135deg, var(--primary-orange), var(--secondary-orange));
|
||||
color: var(--white);
|
||||
border-color: transparent;
|
||||
box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3);
|
||||
}
|
||||
|
||||
.customer-tab.active i {
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.customer-tab i {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Tab Content */
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== Weekly Summary Styles ==================== */
|
||||
.weekly-summary-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 15px;
|
||||
padding: 15px 20px;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.week-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--dark-gray);
|
||||
}
|
||||
|
||||
.week-info i {
|
||||
font-size: 1.2rem;
|
||||
color: var(--primary-orange);
|
||||
}
|
||||
|
||||
.week-navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.week-nav-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: var(--white);
|
||||
color: var(--medium-gray);
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.week-nav-btn:hover {
|
||||
border-color: var(--primary-orange);
|
||||
color: var(--primary-orange);
|
||||
background: rgba(255, 107, 53, 0.05);
|
||||
}
|
||||
|
||||
.week-nav-btn:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.weekly-summary-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.weekly-summary-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
color: var(--medium-gray);
|
||||
}
|
||||
|
||||
.weekly-summary-empty i {
|
||||
font-size: 4rem;
|
||||
color: #ddd;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.weekly-summary-empty p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Customer Summary Card */
|
||||
.customer-summary-card {
|
||||
background: var(--white);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.customer-summary-card:hover {
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.customer-summary-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 15px 20px;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.customer-summary-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.customer-summary-name i {
|
||||
color: var(--primary-orange);
|
||||
}
|
||||
|
||||
.customer-summary-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
background: rgba(255, 107, 53, 0.2);
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--primary-orange);
|
||||
}
|
||||
|
||||
.customer-summary-body {
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.summary-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.summary-section-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--dark-gray);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.summary-section-title i {
|
||||
color: var(--primary-orange);
|
||||
}
|
||||
|
||||
.summary-items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 12px;
|
||||
background: var(--light-gray);
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid var(--primary-orange);
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.summary-item-date {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
min-width: auto;
|
||||
padding: 4px 8px;
|
||||
background: var(--white);
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
color: var(--medium-gray);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.summary-item-date .day {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--dark-gray);
|
||||
}
|
||||
|
||||
.summary-item-content {
|
||||
flex: 1;
|
||||
font-size: 0.85rem;
|
||||
color: var(--dark-gray);
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.summary-item-content.combined-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.content-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.content-label {
|
||||
color: var(--primary-orange);
|
||||
font-size: 0.9rem;
|
||||
min-width: 18px;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
flex: 1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.content-divider {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: var(--border-color);
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.summary-item-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-left: auto;
|
||||
font-size: 0.75rem;
|
||||
color: var(--medium-gray);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.summary-item-meta span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.summary-item-meta i {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
/* Customer Statistics */
|
||||
.customer-summary-stats {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 12px 20px;
|
||||
background: rgba(255, 107, 53, 0.05);
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: var(--medium-gray);
|
||||
}
|
||||
|
||||
.stat-item i {
|
||||
color: var(--primary-orange);
|
||||
}
|
||||
|
||||
.stat-item strong {
|
||||
color: var(--dark-gray);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.customer-tabs {
|
||||
order: 3;
|
||||
width: 100%;
|
||||
margin: 15px 0 0 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.weekly-summary-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.week-info,
|
||||
.week-navigation {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.customer-summary-header {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.customer-summary-stats {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.summary-item-date {
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
@ -141,7 +141,7 @@
|
||||
<div class="action-bar">
|
||||
<button id="addCustomerBtn" class="btn-primary">
|
||||
<i class="fas fa-plus"></i>
|
||||
添加客户
|
||||
添加客户进度
|
||||
</button>
|
||||
<button id="importBtn" class="btn-secondary">
|
||||
<i class="fas fa-file-import"></i>
|
||||
@ -186,7 +186,15 @@
|
||||
<!-- Customer List -->
|
||||
<div class="card table-card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-list"></i> 客户进度</h3>
|
||||
<!-- Tab Navigation (Left Aligned) -->
|
||||
<div class="customer-tabs">
|
||||
<button class="customer-tab active" data-tab="progress-list" id="progressListTab">
|
||||
<i class="fas fa-table"></i> 客户进度
|
||||
</button>
|
||||
<button class="customer-tab" data-tab="weekly-summary" id="weeklySummaryTab">
|
||||
<i class="fas fa-file-alt"></i> 周报汇总
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-actions">
|
||||
<button id="refreshCustomersBtn" class="icon-btn" title="刷新">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
@ -197,54 +205,88 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table id="customerTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>客户</th>
|
||||
<th>咨询时间</th>
|
||||
<th>版本</th>
|
||||
<th>描述</th>
|
||||
<th>解决方案</th>
|
||||
<th>类型</th>
|
||||
<th>模块</th>
|
||||
<th>状态与进度</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="customerTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Progress List View (Default) -->
|
||||
<div id="progressListView" class="tab-content active">
|
||||
<div class="table-responsive">
|
||||
<table id="customerTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>客户</th>
|
||||
<th>咨询时间</th>
|
||||
<th>版本</th>
|
||||
<th>描述</th>
|
||||
<th>解决方案</th>
|
||||
<th>类型</th>
|
||||
<th>模块</th>
|
||||
<th>状态与进度</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="customerTableBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination">
|
||||
<div class="pagination-info">
|
||||
<span id="paginationInfo">显示 0-0 共 0 条</span>
|
||||
</div>
|
||||
<div class="pagination-controls">
|
||||
<button id="firstPage" class="pagination-btn" disabled>
|
||||
<i class="fas fa-angle-double-left"></i>
|
||||
</button>
|
||||
<button id="prevPage" class="pagination-btn" disabled>
|
||||
<i class="fas fa-angle-left"></i>
|
||||
</button>
|
||||
<div id="pageNumbers" class="page-numbers">
|
||||
</div>
|
||||
<button id="nextPage" class="pagination-btn" disabled>
|
||||
<i class="fas fa-angle-right"></i>
|
||||
</button>
|
||||
<button id="lastPage" class="pagination-btn" disabled>
|
||||
<i class="fas fa-angle-double-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pagination-size">
|
||||
<select id="pageSizeSelect">
|
||||
<option value="10">10条/页</option>
|
||||
<option value="20">20条/页</option>
|
||||
<option value="50">50条/页</option>
|
||||
<option value="100">100条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination">
|
||||
<div class="pagination-info">
|
||||
<span id="paginationInfo">显示 0-0 共 0 条</span>
|
||||
</div>
|
||||
<div class="pagination-controls">
|
||||
<button id="firstPage" class="pagination-btn" disabled>
|
||||
<i class="fas fa-angle-double-left"></i>
|
||||
</button>
|
||||
<button id="prevPage" class="pagination-btn" disabled>
|
||||
<i class="fas fa-angle-left"></i>
|
||||
</button>
|
||||
<div id="pageNumbers" class="page-numbers">
|
||||
<!-- Weekly Summary View -->
|
||||
<div id="weeklySummaryView" class="tab-content">
|
||||
<div class="weekly-summary-header">
|
||||
<div class="week-info">
|
||||
<i class="fas fa-calendar-week"></i>
|
||||
<span id="currentWeekRange">本周</span>
|
||||
</div>
|
||||
<button id="nextPage" class="pagination-btn" disabled>
|
||||
<i class="fas fa-angle-right"></i>
|
||||
</button>
|
||||
<button id="lastPage" class="pagination-btn" disabled>
|
||||
<i class="fas fa-angle-double-right"></i>
|
||||
<div class="week-navigation">
|
||||
<button id="prevWeekBtn" class="week-nav-btn" title="上一周">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</button>
|
||||
<button id="currentWeekBtn" class="week-nav-btn" title="回到本周">
|
||||
<i class="fas fa-home"></i> 本周
|
||||
</button>
|
||||
<button id="nextWeekBtn" class="week-nav-btn" title="下一周">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button id="exportWeeklySummaryBtn" class="btn-secondary" title="导出周报">
|
||||
<i class="fas fa-file-export"></i> 导出周报
|
||||
</button>
|
||||
</div>
|
||||
<div class="pagination-size">
|
||||
<select id="pageSizeSelect">
|
||||
<option value="10">10条/页</option>
|
||||
<option value="20">20条/页</option>
|
||||
<option value="50">50条/页</option>
|
||||
<option value="100">100条/页</option>
|
||||
</select>
|
||||
<div id="weeklySummaryContainer" class="weekly-summary-container">
|
||||
<!-- Weekly summary cards will be rendered here -->
|
||||
<div class="weekly-summary-empty">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<p>本周暂无客户进度数据</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2413,4 +2413,345 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// Customer Progress Tabs
|
||||
// ==========================================
|
||||
const progressListTab = document.getElementById('progressListTab');
|
||||
const weeklySummaryTab = document.getElementById('weeklySummaryTab');
|
||||
const progressListView = document.getElementById('progressListView');
|
||||
const weeklySummaryView = document.getElementById('weeklySummaryView');
|
||||
|
||||
// Week offset for navigation (0 = current week, -1 = last week, etc.)
|
||||
let weekOffset = 0;
|
||||
|
||||
// Tab switching
|
||||
function switchCustomerTab(tabName) {
|
||||
// Update tab buttons
|
||||
document.querySelectorAll('.customer-tab').forEach(tab => {
|
||||
tab.classList.remove('active');
|
||||
});
|
||||
|
||||
// Update tab content
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
// Get the table actions (refresh & export buttons)
|
||||
const tableActions = document.querySelector('#customerSection .card-header .table-actions');
|
||||
|
||||
if (tabName === 'progress-list') {
|
||||
progressListTab?.classList.add('active');
|
||||
progressListView?.classList.add('active');
|
||||
// Show refresh and export buttons for progress list
|
||||
if (tableActions) tableActions.style.display = 'flex';
|
||||
} else if (tabName === 'weekly-summary') {
|
||||
weeklySummaryTab?.classList.add('active');
|
||||
weeklySummaryView?.classList.add('active');
|
||||
// Hide refresh and export buttons for weekly summary
|
||||
if (tableActions) tableActions.style.display = 'none';
|
||||
// Load weekly summary when switching to this tab
|
||||
weekOffset = 0;
|
||||
loadWeeklySummary();
|
||||
}
|
||||
}
|
||||
|
||||
// Tab click handlers
|
||||
progressListTab?.addEventListener('click', () => switchCustomerTab('progress-list'));
|
||||
weeklySummaryTab?.addEventListener('click', () => switchCustomerTab('weekly-summary'));
|
||||
|
||||
// Week navigation handlers
|
||||
document.getElementById('prevWeekBtn')?.addEventListener('click', () => {
|
||||
weekOffset--;
|
||||
loadWeeklySummary();
|
||||
});
|
||||
|
||||
document.getElementById('nextWeekBtn')?.addEventListener('click', () => {
|
||||
weekOffset++;
|
||||
loadWeeklySummary();
|
||||
});
|
||||
|
||||
document.getElementById('currentWeekBtn')?.addEventListener('click', () => {
|
||||
weekOffset = 0;
|
||||
loadWeeklySummary();
|
||||
});
|
||||
|
||||
// Get week date range
|
||||
function getWeekRange(offset = 0) {
|
||||
const now = new Date();
|
||||
const currentDay = now.getDay();
|
||||
const diff = currentDay === 0 ? 6 : currentDay - 1; // Adjust for Monday start
|
||||
|
||||
const monday = new Date(now);
|
||||
monday.setDate(now.getDate() - diff + (offset * 7));
|
||||
monday.setHours(0, 0, 0, 0);
|
||||
|
||||
const sunday = new Date(monday);
|
||||
sunday.setDate(monday.getDate() + 6);
|
||||
sunday.setHours(23, 59, 59, 999);
|
||||
|
||||
return { start: monday, end: sunday };
|
||||
}
|
||||
|
||||
// Format date for display
|
||||
function formatWeekDate(date) {
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
return `${month}月${day}日`;
|
||||
}
|
||||
|
||||
// Format date to YYYY-MM-DD for comparison
|
||||
function formatDateForCompare(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
// Parse date string from customer data
|
||||
function parseCustomerDate(dateStr) {
|
||||
if (!dateStr) return null;
|
||||
const cleaned = String(dateStr).trim().replace(/\./g, '-').replace(/\//g, '-');
|
||||
const parts = cleaned.split('-').filter(Boolean);
|
||||
if (parts.length < 3) return null;
|
||||
return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
|
||||
}
|
||||
|
||||
// Load and render weekly summary
|
||||
function loadWeeklySummary() {
|
||||
const weekRange = getWeekRange(weekOffset);
|
||||
|
||||
// Update week range display
|
||||
const weekRangeEl = document.getElementById('currentWeekRange');
|
||||
if (weekRangeEl) {
|
||||
const weekLabel = weekOffset === 0 ? '本周' : (weekOffset === -1 ? '上周' : `${Math.abs(weekOffset)}周${weekOffset < 0 ? '前' : '后'}`);
|
||||
weekRangeEl.textContent = `${weekLabel}: ${formatWeekDate(weekRange.start)} - ${formatWeekDate(weekRange.end)}`;
|
||||
}
|
||||
|
||||
// Filter customers by week
|
||||
const weekStart = formatDateForCompare(weekRange.start);
|
||||
const weekEnd = formatDateForCompare(weekRange.end);
|
||||
|
||||
const weeklyCustomers = allCustomers.filter(customer => {
|
||||
const customerDate = parseCustomerDate(customer.intendedProduct);
|
||||
if (!customerDate) return false;
|
||||
const dateStr = formatDateForCompare(customerDate);
|
||||
return dateStr >= weekStart && dateStr <= weekEnd;
|
||||
});
|
||||
|
||||
// Group by customer name
|
||||
const customerGroups = {};
|
||||
weeklyCustomers.forEach(customer => {
|
||||
const name = customer.customerName || '未知客户';
|
||||
if (!customerGroups[name]) {
|
||||
customerGroups[name] = [];
|
||||
}
|
||||
customerGroups[name].push(customer);
|
||||
});
|
||||
|
||||
// Render weekly summary
|
||||
renderWeeklySummary(customerGroups);
|
||||
}
|
||||
|
||||
// Render weekly summary cards
|
||||
function renderWeeklySummary(customerGroups) {
|
||||
const container = document.getElementById('weeklySummaryContainer');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
const customerNames = Object.keys(customerGroups);
|
||||
|
||||
if (customerNames.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="weekly-summary-empty">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<p>本周暂无客户进度数据</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort customer names
|
||||
customerNames.sort();
|
||||
|
||||
customerNames.forEach(customerName => {
|
||||
const records = customerGroups[customerName];
|
||||
const card = createCustomerSummaryCard(customerName, records);
|
||||
container.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Create customer summary card
|
||||
function createCustomerSummaryCard(customerName, records) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'customer-summary-card';
|
||||
|
||||
// Sort records by date
|
||||
records.sort((a, b) => {
|
||||
const dateA = parseCustomerDate(a.intendedProduct) || new Date(0);
|
||||
const dateB = parseCustomerDate(b.intendedProduct) || new Date(0);
|
||||
return dateB - dateA;
|
||||
});
|
||||
|
||||
// Count statistics
|
||||
const totalRecords = records.length;
|
||||
|
||||
// Get unique types and modules
|
||||
const types = [...new Set(records.map(r => r.type).filter(Boolean))];
|
||||
const modules = [...new Set(records.map(r => r.module).filter(Boolean))];
|
||||
|
||||
// Build combined items (description + solution in one row)
|
||||
let combinedItems = '';
|
||||
records.forEach(record => {
|
||||
const date = parseCustomerDate(record.intendedProduct);
|
||||
const dateDisplay = date ? formatItemDate(date) : '';
|
||||
const description = record.description && record.description.trim() ? escapeHtml(record.description) : '-';
|
||||
const solution = record.solution && record.solution.trim() ? escapeHtml(record.solution) : '-';
|
||||
|
||||
combinedItems += `
|
||||
<div class="summary-item">
|
||||
${dateDisplay}
|
||||
<div class="summary-item-content combined-content">
|
||||
<div class="content-row">
|
||||
<span class="content-label"><i class="fas fa-clipboard-list"></i></span>
|
||||
<span class="content-text">${description}</span>
|
||||
</div>
|
||||
<div class="content-divider"></div>
|
||||
<div class="content-row">
|
||||
<span class="content-label"><i class="fas fa-lightbulb"></i></span>
|
||||
<span class="content-text">${solution}</span>
|
||||
</div>
|
||||
<div class="summary-item-meta">
|
||||
${record.version ? `<span><i class="fas fa-code-branch"></i> ${escapeHtml(record.version)}</span>` : ''}
|
||||
${record.type ? `<span><i class="fas fa-tag"></i> ${escapeHtml(record.type)}</span>` : ''}
|
||||
${record.module ? `<span><i class="fas fa-puzzle-piece"></i> ${escapeHtml(record.module)}</span>` : ''}
|
||||
${record.statusProgress ? `<span><i class="fas fa-tasks"></i> ${escapeHtml(record.statusProgress)}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="customer-summary-header">
|
||||
<div class="customer-summary-name">
|
||||
<i class="fas fa-building"></i>
|
||||
<span>${escapeHtml(customerName)}</span>
|
||||
</div>
|
||||
<div class="customer-summary-badge">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
<span>${totalRecords} 条记录</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customer-summary-body">
|
||||
<div class="summary-items">
|
||||
${combinedItems || '<div class="weekly-summary-empty" style="padding: 20px;"><p>暂无数据</p></div>'}
|
||||
</div>
|
||||
</div>
|
||||
<div class="customer-summary-stats">
|
||||
<div class="stat-item">
|
||||
<i class="fas fa-tag"></i>
|
||||
<span>类型: <strong>${types.join(', ') || '-'}</strong></span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<i class="fas fa-puzzle-piece"></i>
|
||||
<span>模块: <strong>${modules.join(', ') || '-'}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// Format date for summary item
|
||||
function formatItemDate(date) {
|
||||
const dayNames = ['日', '一', '二', '三', '四', '五', '六'];
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const dayOfWeek = dayNames[date.getDay()];
|
||||
|
||||
return `
|
||||
<div class="summary-item-date">
|
||||
<span class="day">${month}/${day}</span>
|
||||
<span>周${dayOfWeek}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Escape HTML to prevent XSS
|
||||
function escapeHtml(text) {
|
||||
if (!text) return '';
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Export weekly summary
|
||||
document.getElementById('exportWeeklySummaryBtn')?.addEventListener('click', () => {
|
||||
const weekRange = getWeekRange(weekOffset);
|
||||
const weekStart = formatDateForCompare(weekRange.start);
|
||||
const weekEnd = formatDateForCompare(weekRange.end);
|
||||
|
||||
const weeklyCustomers = allCustomers.filter(customer => {
|
||||
const customerDate = parseCustomerDate(customer.intendedProduct);
|
||||
if (!customerDate) return false;
|
||||
const dateStr = formatDateForCompare(customerDate);
|
||||
return dateStr >= weekStart && dateStr <= weekEnd;
|
||||
});
|
||||
|
||||
// Group by customer name
|
||||
const customerGroups = {};
|
||||
weeklyCustomers.forEach(customer => {
|
||||
const name = customer.customerName || '未知客户';
|
||||
if (!customerGroups[name]) {
|
||||
customerGroups[name] = [];
|
||||
}
|
||||
customerGroups[name].push(customer);
|
||||
});
|
||||
|
||||
// Build export content
|
||||
let content = `周报汇总 (${formatWeekDate(weekRange.start)} - ${formatWeekDate(weekRange.end)})\n`;
|
||||
content += '='.repeat(60) + '\n\n';
|
||||
|
||||
Object.keys(customerGroups).sort().forEach(customerName => {
|
||||
const records = customerGroups[customerName];
|
||||
content += `【${customerName}】\n`;
|
||||
content += '-'.repeat(40) + '\n';
|
||||
|
||||
content += '\n▶ 问题描述:\n';
|
||||
records.forEach(record => {
|
||||
if (record.description && record.description.trim()) {
|
||||
const date = parseCustomerDate(record.intendedProduct);
|
||||
const dateStr = date ? `${date.getMonth() + 1}/${date.getDate()}` : '';
|
||||
content += ` [${dateStr}] ${record.description}\n`;
|
||||
if (record.version) content += ` 版本: ${record.version}\n`;
|
||||
if (record.type) content += ` 类型: ${record.type}\n`;
|
||||
if (record.statusProgress) content += ` 状态: ${record.statusProgress}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
content += '\n▶ 解决方案:\n';
|
||||
records.forEach(record => {
|
||||
if (record.solution && record.solution.trim()) {
|
||||
const date = parseCustomerDate(record.intendedProduct);
|
||||
const dateStr = date ? `${date.getMonth() + 1}/${date.getDate()}` : '';
|
||||
content += ` [${dateStr}] ${record.solution}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
content += '\n';
|
||||
});
|
||||
|
||||
// Download as text file
|
||||
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `周报汇总_${formatDateForCompare(weekRange.start)}_${formatDateForCompare(weekRange.end)}.txt`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user