diff --git a/frontend/index.html b/frontend/index.html
index b233c2b..fd7a66a 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -479,10 +479,16 @@
diff --git a/frontend/js/main.js b/frontend/js/main.js
index 41341f2..388fae5 100644
--- a/frontend/js/main.js
+++ b/frontend/js/main.js
@@ -61,7 +61,7 @@ function canDelete() {
}
document.addEventListener("DOMContentLoaded", function () {
- console.log("CRM System Main JS v4.0 Loaded - " + new Date().toLocaleString());
+ console.log("CRM System Main JS v4.5 Loaded - " + new Date().toLocaleString());
// 登录守卫
const token = localStorage.getItem("crmToken");
if (!token && !window.location.pathname.endsWith("login.html")) {
@@ -1653,7 +1653,9 @@ document.addEventListener("DOMContentLoaded", function () {
const ctx = canvas.getContext("2d");
const trendType =
- document.getElementById("trendTypeSelect")?.value || "customer";
+ document.getElementById("trendTypeSelect")?.value || "all";
+ const dateGrain =
+ document.getElementById("trendChartFieldSelect")?.value || "day";
if (trendChartInstance) {
trendChartInstance.destroy();
@@ -1663,11 +1665,23 @@ document.addEventListener("DOMContentLoaded", function () {
const dateMap = {};
customers.forEach((customer) => {
- const dateStr = normalizeDateValue(customer.intendedProduct);
- if (!dateStr) return;
+ const dateValue = normalizeDateValue(customer.intendedProduct);
+ if (!dateValue) return;
- if (!dateMap[dateStr]) {
- dateMap[dateStr] = {
+ let dateKey = dateValue;
+ if (dateGrain === "month") {
+ dateKey = dateValue.substring(0, 7); // YYYY-MM
+ } else if (dateGrain === "week") {
+ // Find Monday of that week
+ const d = new Date(dateValue);
+ const day = d.getDay();
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1);
+ const monday = new Date(d.setDate(diff));
+ dateKey = monday.toISOString().split("T")[0] + " (周)";
+ }
+
+ if (!dateMap[dateKey]) {
+ dateMap[dateKey] = {
customers: new Set(),
demands: 0,
issues: 0,
@@ -1676,16 +1690,20 @@ document.addEventListener("DOMContentLoaded", function () {
// Count customers
if (customer.customerName) {
- dateMap[dateStr].customers.add(customer.customerName);
+ dateMap[dateKey].customers.add(customer.customerName);
}
// Count demands and issues based on type
const type = (customer.type || "").toLowerCase();
if (type.includes("需求")) {
- dateMap[dateStr].demands++;
+ dateMap[dateKey].demands++;
}
- if (type.includes("问题") || type.includes("功能问题")) {
- dateMap[dateStr].issues++;
+ if (
+ type.includes("问题") ||
+ type.includes("功能问题") ||
+ type.includes("反馈")
+ ) {
+ dateMap[dateKey].issues++;
}
});
@@ -1695,96 +1713,60 @@ document.addEventListener("DOMContentLoaded", function () {
// Prepare datasets based on selected trend type
const datasets = [];
+ const customerDataset = {
+ label: "客户数",
+ data: sortedDates.map((date) => dateMap[date].customers.size),
+ borderColor: "#FF6B35",
+ backgroundColor: "rgba(255, 107, 53, 0.1)",
+ borderWidth: 3,
+ pointRadius: 5,
+ pointHoverRadius: 7,
+ pointBackgroundColor: "#FF6B35",
+ pointBorderColor: "#fff",
+ pointBorderWidth: 2,
+ tension: 0.4,
+ fill: trendType === "customer",
+ };
+
+ const demandDataset = {
+ label: "需求数",
+ data: sortedDates.map((date) => dateMap[date].demands),
+ borderColor: "#4CAF50",
+ backgroundColor: "rgba(76, 175, 80, 0.1)",
+ borderWidth: 3,
+ pointRadius: 5,
+ pointHoverRadius: 7,
+ pointBackgroundColor: "#4CAF50",
+ pointBorderColor: "#fff",
+ pointBorderWidth: 2,
+ tension: 0.4,
+ fill: trendType === "demand",
+ };
+
+ const issueDataset = {
+ label: "反馈数",
+ data: sortedDates.map((date) => dateMap[date].issues),
+ borderColor: "#2196F3",
+ backgroundColor: "rgba(33, 150, 243, 0.1)",
+ borderWidth: 3,
+ pointRadius: 5,
+ pointHoverRadius: 7,
+ pointBackgroundColor: "#2196F3",
+ pointBorderColor: "#fff",
+ pointBorderWidth: 2,
+ tension: 0.4,
+ fill: trendType === "issue",
+ };
+
if (trendType === "customer") {
- datasets.push({
- label: "客户数",
- data: sortedDates.map((date) => dateMap[date].customers.size),
- borderColor: "#FF6B35",
- backgroundColor: "rgba(255, 107, 53, 0.1)",
- borderWidth: 3,
- pointRadius: 5,
- pointHoverRadius: 7,
- pointBackgroundColor: "#FF6B35",
- pointBorderColor: "#fff",
- pointBorderWidth: 2,
- tension: 0.4,
- fill: true,
- });
+ datasets.push(customerDataset);
} else if (trendType === "demand") {
- datasets.push({
- label: "需求数",
- data: sortedDates.map((date) => dateMap[date].demands),
- borderColor: "#4CAF50",
- backgroundColor: "rgba(76, 175, 80, 0.1)",
- borderWidth: 3,
- pointRadius: 5,
- pointHoverRadius: 7,
- pointBackgroundColor: "#4CAF50",
- pointBorderColor: "#fff",
- pointBorderWidth: 2,
- tension: 0.4,
- fill: true,
- });
+ datasets.push(demandDataset);
} else if (trendType === "issue") {
- datasets.push({
- label: "问题数",
- data: sortedDates.map((date) => dateMap[date].issues),
- borderColor: "#F28C28",
- backgroundColor: "rgba(242, 140, 40, 0.1)",
- borderWidth: 3,
- pointRadius: 5,
- pointHoverRadius: 7,
- pointBackgroundColor: "#F28C28",
- pointBorderColor: "#fff",
- pointBorderWidth: 2,
- tension: 0.4,
- fill: true,
- });
- } else if (trendType === "all") {
- datasets.push(
- {
- label: "客户数",
- data: sortedDates.map((date) => dateMap[date].customers.size),
- borderColor: "#FF6B35",
- backgroundColor: "rgba(255, 107, 53, 0.1)",
- borderWidth: 3,
- pointRadius: 5,
- pointHoverRadius: 7,
- pointBackgroundColor: "#FF6B35",
- pointBorderColor: "#fff",
- pointBorderWidth: 2,
- tension: 0.4,
- fill: false,
- },
- {
- label: "需求数",
- data: sortedDates.map((date) => dateMap[date].demands),
- borderColor: "#4CAF50",
- backgroundColor: "rgba(76, 175, 80, 0.1)",
- borderWidth: 3,
- pointRadius: 5,
- pointHoverRadius: 7,
- pointBackgroundColor: "#4CAF50",
- pointBorderColor: "#fff",
- pointBorderWidth: 2,
- tension: 0.4,
- fill: false,
- },
- {
- label: "问题数",
- data: sortedDates.map((date) => dateMap[date].issues),
- borderColor: "#F28C28",
- backgroundColor: "rgba(242, 140, 40, 0.1)",
- borderWidth: 3,
- pointRadius: 5,
- pointHoverRadius: 7,
- pointBackgroundColor: "#F28C28",
- pointBorderColor: "#fff",
- pointBorderWidth: 2,
- tension: 0.4,
- fill: false,
- },
- );
+ datasets.push(issueDataset);
+ } else {
+ // all
+ datasets.push(customerDataset, demandDataset, issueDataset);
}
trendChartInstance = new Chart(ctx, {
@@ -1928,6 +1910,15 @@ document.addEventListener("DOMContentLoaded", function () {
});
}
+ const trendChartFieldSelect = document.getElementById("trendChartFieldSelect");
+ if (trendChartFieldSelect) {
+ trendChartFieldSelect.addEventListener("change", function () {
+ const startDate = document.getElementById("startDate").value;
+ const endDate = document.getElementById("endDate").value;
+ applyDateFilter(startDate, endDate);
+ });
+ }
+
// ========== Follow-up Management ==========
const followupSection = document.getElementById("followupSection");
const addFollowUpBtn = document.getElementById("addFollowUpBtn");