feat_ui
This commit is contained in:
parent
ceb0f74ccc
commit
97d5f51ff2
@ -2244,4 +2244,395 @@ tr:hover .action-cell {
|
|||||||
|
|
||||||
.table-refreshing tbody tr {
|
.table-refreshing tbody tr {
|
||||||
animation: rowHighlight 0.4s ease-out;
|
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">
|
<div class="action-bar">
|
||||||
<button id="addCustomerBtn" class="btn-primary">
|
<button id="addCustomerBtn" class="btn-primary">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus"></i>
|
||||||
添加客户
|
添加客户进度
|
||||||
</button>
|
</button>
|
||||||
<button id="importBtn" class="btn-secondary">
|
<button id="importBtn" class="btn-secondary">
|
||||||
<i class="fas fa-file-import"></i>
|
<i class="fas fa-file-import"></i>
|
||||||
@ -186,7 +186,15 @@
|
|||||||
<!-- Customer List -->
|
<!-- Customer List -->
|
||||||
<div class="card table-card">
|
<div class="card table-card">
|
||||||
<div class="card-header">
|
<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">
|
<div class="table-actions">
|
||||||
<button id="refreshCustomersBtn" class="icon-btn" title="刷新">
|
<button id="refreshCustomersBtn" class="icon-btn" title="刷新">
|
||||||
<i class="fas fa-sync-alt"></i>
|
<i class="fas fa-sync-alt"></i>
|
||||||
@ -197,54 +205,88 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="table-responsive">
|
<!-- Progress List View (Default) -->
|
||||||
<table id="customerTable">
|
<div id="progressListView" class="tab-content active">
|
||||||
<thead>
|
<div class="table-responsive">
|
||||||
<tr>
|
<table id="customerTable">
|
||||||
<th>客户</th>
|
<thead>
|
||||||
<th>咨询时间</th>
|
<tr>
|
||||||
<th>版本</th>
|
<th>客户</th>
|
||||||
<th>描述</th>
|
<th>咨询时间</th>
|
||||||
<th>解决方案</th>
|
<th>版本</th>
|
||||||
<th>类型</th>
|
<th>描述</th>
|
||||||
<th>模块</th>
|
<th>解决方案</th>
|
||||||
<th>状态与进度</th>
|
<th>类型</th>
|
||||||
<th>操作</th>
|
<th>模块</th>
|
||||||
</tr>
|
<th>状态与进度</th>
|
||||||
</thead>
|
<th>操作</th>
|
||||||
<tbody id="customerTableBody">
|
</tr>
|
||||||
</tbody>
|
</thead>
|
||||||
</table>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Weekly Summary View -->
|
||||||
<div class="pagination">
|
<div id="weeklySummaryView" class="tab-content">
|
||||||
<div class="pagination-info">
|
<div class="weekly-summary-header">
|
||||||
<span id="paginationInfo">显示 0-0 共 0 条</span>
|
<div class="week-info">
|
||||||
</div>
|
<i class="fas fa-calendar-week"></i>
|
||||||
<div class="pagination-controls">
|
<span id="currentWeekRange">本周</span>
|
||||||
<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>
|
</div>
|
||||||
<button id="nextPage" class="pagination-btn" disabled>
|
<div class="week-navigation">
|
||||||
<i class="fas fa-angle-right"></i>
|
<button id="prevWeekBtn" class="week-nav-btn" title="上一周">
|
||||||
</button>
|
<i class="fas fa-chevron-left"></i>
|
||||||
<button id="lastPage" class="pagination-btn" disabled>
|
</button>
|
||||||
<i class="fas fa-angle-double-right"></i>
|
<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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagination-size">
|
<div id="weeklySummaryContainer" class="weekly-summary-container">
|
||||||
<select id="pageSizeSelect">
|
<!-- Weekly summary cards will be rendered here -->
|
||||||
<option value="10">10条/页</option>
|
<div class="weekly-summary-empty">
|
||||||
<option value="20">20条/页</option>
|
<i class="fas fa-inbox"></i>
|
||||||
<option value="50">50条/页</option>
|
<p>本周暂无客户进度数据</p>
|
||||||
<option value="100">100条/页</option>
|
</div>
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</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