使用-HTML-CSS-和-JavaScript-构建数据仪表盘
使用 HTML、CSS 和 JavaScript 构建数据仪表盘
原文:
towardsdatascience.com/build-a-data-dashboard-using-html-css-javascript/
对于没有 HTML、JavaScript 等经验的 Python 开发者来说,这不再是借口,因为过去几年中已经出现了许多 Python 库,例如 Streamlit 和 Gradio。
这篇文章并不是关于他们的,因为我就是那些直言不讳的 Python 开发者之一,我已经尝试过 Streamlit 和 Gradio。所以现在是时候卷起袖子,看看我是否能够学习新技能并使用那些老牌前端开发技术:HTML、JavaScript 和 CSS 来创建仪表盘了。
我们仪表板的数据将来自本地 SQLite 数据库。我在 SQLite 中创建了一个名为sales_data的表,其中包含模拟的销售数据。以下是表格形式的数据。

图片由作者提供
下面是一些你可以用来跟随并创建自己的 SQLite 数据库和表的代码,其中包含所示的数据。
如果你想知道为什么我只在我的数据库中插入了一小部分记录,那并不是因为我认为代码无法处理大量数据。只是我想专注于仪表盘功能,而不是被数据分散注意力。如果你喜欢,可以使用我下面提供的脚本向输入数据集添加额外的记录。
因此,我们在设置 SQLite 数据库时仍然停留在 Python 的世界中。
import sqlite3
# Define the database name
DATABASE_NAME = "C:\\Users\\thoma\\projects\\my-dashboard\\sales_data.db"
# Connect to SQLite database
conn = sqlite3.connect(DATABASE_NAME)
# Create a cursor object
cursor = conn.cursor()
# SQL to create the 'sales' table
create_table_query = '''
CREATE TABLE IF NOT EXISTS sales (
order_id INTEGER PRIMARY KEY,
order_date TEXT,
customer_id INTEGER,
customer_name TEXT,
product_id INTEGER,
product_names TEXT,
categories TEXT,
quantity INTEGER,
price REAL,
total REAL
);
'''
# Execute the query to create the table
cursor.execute(create_table_query)
# Sample data to insert into the 'sales' table
sample_data = [
(1, "2022-08-01", 245, "Customer_884", 201, "Smartphone", "Electronics", 3, 90.02, 270.06),
(2, "2022-02-19", 701, "Customer_1672", 205, "Printer", "Electronics", 6, 12.74, 76.44),
(3, "2017-01-01", 184, "Customer_21720", 208, "Notebook", "Stationery", 8, 48.35, 386.80),
(4, "2013-03-09", 275, "Customer_23770", 200, "Laptop", "Electronics", 3, 74.85, 224.55),
(5, "2022-04-23", 960, "Customer_23790", 210, "Cabinet", "Office", 6, 53.77, 322.62),
(6, "2019-07-10", 197, "Customer_25587", 202, "Desk", "Office", 3, 47.17, 141.51),
(7, "2014-11-12", 510, "Customer_6912", 204, "Monitor", "Electronics", 5, 22.5, 112.5),
(8, "2016-07-12", 150, "Customer_17761", 200, "Laptop", "Electronics", 9, 49.33, 443.97)
]
# SQL to insert data into the 'sales' table
insert_data_query = '''
INSERT INTO sales (order_id, order_date, customer_id, customer_name, product_id, product_names, categories, quantity, price, total)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
# Insert the sample data
cursor.executemany(insert_data_query, sample_data)
# Commit the transaction
conn.commit()
# Close the connection
conn.close()
print(f"Database '{DATABASE_NAME}' has been created and populated successfully.")
仪表盘功能
我们的仪表盘将具有以下功能。
-
关键指标。总收入、总订单数、平均订单价值、顶级类别
-
不同的图表类型。随时间变化的收入(折线图)、按类别划分的收入(柱状图)、按收入排名的前产品(水平柱状图)
-
过滤。按日期和类别
-
数据表。以分页和可搜索的网格格式显示我们的数据记录。
设置我们的环境
接下来,我们将遵循一系列步骤来设置我们的环境。
1/ 安装 Node.js。
Node.js 是一个运行时环境,它允许你在浏览器之外运行 JavaScript,让你可以使用 JavaScript 构建快速且可扩展的服务器端应用程序。
因此,请确保您的系统已安装 Node.js,以便您能够运行本地服务器并管理包。您可以从 Node.js 官方网站 下载它。
2/ 创建主项目文件夹和子文件夹
打开您的命令终端并运行以下命令。我在 Windows 磁盘上使用 Ubuntu,但您可以根据您首选的命令行工具和系统进行更改。
$ mkdir my-dashboard
$ cd my-dashboard
$ mkdir client
% mkdir server
3/ 初始化 Node 项目
$ npm init -y
此命令会自动在您的项目目录中创建一个默认的 package.json 文件,而无需用户输入。
-y 标志对所有提示回答 “是”,使用字段的 默认值,例如:
-
名称
-
版本
-
描述
-
主
-
脚本
-
作者
-
许可证
这就是我的包文件看起来像什么。
{
"name": "my-dashboard",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"express": "⁴.21.2",
"sqlite3": "⁵.1.7"
}
}
4/ 安装 Express 和 SQLite
SQLite 是一个轻量级的基于文件的数据库引擎,它将所有数据存储在一个单一的、可移植的文件中,消除了需要单独服务器的需求。
Express 是一个用于 Node.js 的最小、灵活的 Web 应用程序框架,通过路由和中间件简化了 API 和 Web 服务器的构建。
我们可以使用以下命令安装它们。
$ npm install express sqlite3
现在,我们可以开始编写代码了。对于这个项目,我们需要四个代码文件:一个 index.html 文件、一个 server.js 文件、一个 client.js 文件和一个 script.js 文件。
让我们一步一步地了解它们。
1) 客户端/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<link rel="stylesheet" href="style.css">
<title>Sales Performance Dashboard</title>
</head>
<body>
<div class="container">
<!-- Centered Heading -->
<h1 class="text-center">Sales Performance Dashboard</h1>
<!-- Filter Section -->
<div class="filters row my-4">
<div class="col-md-4">
<label for="start-date">Start Date</label>
<input type="text" id="start-date" class="form-control" placeholder="Start Date">
</div>
<div class="col-md-4">
<label for="end-date">End Date</label>
<input type="text" id="end-date" class="form-control" placeholder="End Date">
</div>
<div class="col-md-4">
<label for="category-filter">Category</label>
<select id="category-filter" class="form-control">
<option value="all">All Categories</option>
<!-- Options will be populated dynamically -->
</select>
</div>
</div>
<!-- Key Metrics Section -->
<h2 class="mt-5">Key Metrics</h2> <!-- Added heading for Key Metrics -->
<div id="key-metrics" class="row text-center my-4">
<div class="col-md-3">
<h4>Total Revenue</h4>
<p id="total-revenue">$0</p>
</div>
<div class="col-md-3">
<h4>Total Orders</h4>
<p id="total-orders">0</p>
</div>
<div class="col-md-3">
<h4>Average Order Value</h4>
<p id="average-order-value">$0</p>
</div>
<div class="col-md-3">
<h4>Top Category</h4>
<p id="top-category">None</p>
</div>
</div>
<!-- Chart Section -->
<div class="chart-section my-4">
<label for="chart-type-selector">Select Chart:</label>
<select id="chart-type-selector" class="form-control mb-3">
<option value="revenueOverTime">Revenue Over Time</option>
<option value="revenueByCategory">Revenue By Category</option>
<option value="topProducts">Top Products by Revenue</option>
</select>
<canvas id="chart-canvas"></canvas>
</div>
<!-- Raw Data Table Section -->
<div id="raw-data" class="my-4">
<h3>Raw Data</h3>
<table id="data-table" class="table table-striped table-bordered"></table>
</div>
</div>
<!-- Required JS Libraries -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="script.js"></script>
</body>
</html>
此 HTML 文件建立了我们的销售绩效仪表板的基本视觉元素,包括日期和类别的交互式过滤器、显示关键销售指标的章节、选择图表类型的下拉菜单以及用于原始数据的表格。
Bootstrap 用于样式设计。Flatpickr 用于日期输入。Chart.js 用于可视化,DataTables 用于表格显示。交互性由外部的 script.js 文件处理,我们稍后会对其进行检查。
Bootstrap 是一个流行的前端框架,最初由 Twitter 开发,它帮助您更轻松、更快速地构建响应式和视觉上一致的 Web 界面。
DataTables 是一个基于 jQuery 的插件,它增强了标准的 HTML
元素,将它们转换成完全交互式、功能丰富的表格。Flatpickr 是一个轻量级、可定制的 JavaScript 日期和时间选择器。它允许用户从简洁的弹出日历中选择日期(以及可选的时间),而不是手动输入。
Chart.js 是一个简单而强大的 JavaScript 库,用于在 Web 应用程序中使用
2) 客户端/style.css
/* client/style.css */
body {
background-color: #f8f9fa;
font-family: 'Arial', sans-serif;
}
h1 {
text-align: center; /* Center the heading */
margin-top: 20px; /* Add spacing above the heading */
margin-bottom: 40px; /* Add spacing below the heading */
}
.container .filters {
margin-top: 20px;
margin-bottom: 60px !important; /* Ensure larger spacing between filters and Key Metrics */
}
.container #key-metrics {
margin-top: 40px !important; /* Additional spacing above the Key Metrics section */
margin-bottom: 20px; /* Optional spacing below */
}
.key-metrics div {
margin: 10px 0;
padding: 10px;
background-color: #f4f4f4;
border: 1px solid #ccc;
border-radius: 4px;
}
/* Fix for DataTables Pagination Spacing */
.dataTables_wrapper .dataTables_paginate {
text-align: center;
margin-top: 10px;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
margin: 0 12px;
padding: 5px 10px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
color: #007bff;
text-decoration: none;
display: inline-block;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
background-color: #007bff;
color: #fff;
border: 1px solid #007bff;
}
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
font-weight: bold;
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
我们使用级联样式表(CSS)来设计仪表板的基本视觉组件,例如按钮和文本颜色、元素间的间距等。
style.css 文件为仪表板提供外观和整体外观。它是一个干净、轻快的主题,具有充足的间距和布局调整,以提高清晰度和可读性。style.css 文件还自定义了 DataTables 分页按钮的外观,使其更用户友好,并与 Bootstrap 的设计视觉上保持一致。
3) server/server.js
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const app = express();
const PORT = 3000;
// Full path to your SQLite database
const DB_PATH = "C:\\Users\\thoma\\projects\\my-dashboard\\sales_data.db";
// Serve static files from the client directory
app.use(express.static(path.join(__dirname, '..', 'client')));
// Route to fetch data from SQLite database
app.get('/data', (req, res) => {
const db = new sqlite3.Database(DB_PATH, sqlite3.OPEN_READONLY, (err) => {
if (err) {
console.error("Error connecting to database:", err.message);
res.status(500).json({ error: "Database connection failed" });
return;
}
});
// Query the database
const query = "SELECT * FROM sales;"; // Replace 'sales' with your table name
db.all(query, [], (err, rows) => {
if (err) {
console.error("Error running query:", err.message);
res.status(500).json({ error: "Query failed" });
} else {
res.json(rows); // Send the query result as JSON
}
});
db.close((err) => {
if (err) {
console.error("Error closing database:", err.message);
}
});
});
// Catch-all route to serve the main HTML file
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '..', 'client', 'index.html'));
});
// Start the server
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
此 Node.js 脚本包含设置基本 Express 服务器所用的 JavaScript 代码,该服务器为销售绩效仪表板提供动力。它做两件事:
-
从客户端子文件夹中提供静态文件(如 HTML、CSS 和 JS),以便前端在浏览器中加载。
-
提供一个 /data 端点,该端点从本地 SQLite 数据库(sales_data.db)读取并返回整个销售表作为 JSON,从而在前端启用动态数据可视化和表格。
注意:在发布后,一位读者在运行上述代码时指出以下错误。
PS D:\repo\my-dashboard\server> node server.js
D:\repo\my-dashboard\node_modules\path-to-regexp\dist\index.js:96
throw new PathError(`Missing parameter name at index ${index}`, str);
^
PathError [TypeError]: Missing parameter name at index 1: *; visit https://git.new/pathToRegexpError for info
at name (D:\repo\my-dashboard\node_modules\path-to-regexp\dist\index.js:96:19)
at parse (D:\repo\my-dashboard\node_modules\path-to-regexp\dist\index.js:113:68)
at pathToRegexp (D:\repo\my-dashboard\node_modules\path-to-regexp\dist\index.js:267:58)
at Object.match (D:\repo\my-dashboard\node_modules\path-to-regexp\dist\index.js:237:30)
at matcher (D:\repo\my-dashboard\node_modules\router\lib\layer.js:86:23)
at new Layer (D:\repo\my-dashboard\node_modules\router\lib\layer.js:93:62)
at router.route (D:\repo\my-dashboard\node_modules\router\index.js:428:17)
at app.route (D:\repo\my-dashboard\node_modules\express\lib\application.js:257:22)
at app.<computed> [as get] (D:\repo\my-dashboard\node_modules\express\lib\application.js:478:22)
at Object.<anonymous> (D:\repo\my-dashboard\server\server.js:42:5) {
originalPath: '*'
}
问题似乎是由于他们使用的 Express 库版本引起的。我建议将此行,
app.get(‘*’, (req, res) => {
改为
app.get(‘/*’, (req, res) => {
最后,经过一些实验,他们发现将线条改为以下所示对他们有效。
app.get(‘/’, (req, res) => {
4) client/script.js
let chartInstance = null; // Global variable to store the current Chart.js instance
// Wait until the DOM is fully loaded
document.addEventListener('DOMContentLoaded', function () {
// Fetch sales data from the backend API
fetch('/data')
.then((response) => response.json())
.then((data) => {
// Handle case where no data is returned
if (!data || data.length === 0) {
const app = document.getElementById('app');
if (app) {
app.innerHTML = "<p>No data available.</p>";
}
return;
}
// Initialize filters and dashboard content
setupFilters(data);
initializeDashboard(data);
// Re-render charts when chart type changes
document.getElementById('chart-type-selector').onchange = () => filterAndRenderData(data);
})
.catch((error) => {
// Handle fetch error
console.error('Error fetching data:', error);
const app = document.getElementById('app');
if (app) {
app.innerHTML = "<p>Failed to fetch data.</p>";
}
});
});
// Initialize Flatpickr date pickers and category filter
function setupFilters(data) {
// Convert date strings to JS Date objects
const dates = data.map((item) => new Date(item.order_date.split('/').reverse().join('-')));
const minDate = new Date(Math.min(...dates));
const maxDate = new Date(Math.max(...dates));
// Configure start date picker
flatpickr("#start-date", {
defaultDate: minDate.toISOString().slice(0, 10),
dateFormat: "Y-m-d",
altInput: true,
altFormat: "F j, Y",
onChange: function () {
filterAndRenderData(data);
},
});
// Configure end date picker
flatpickr("#end-date", {
defaultDate: maxDate.toISOString().slice(0, 10),
dateFormat: "Y-m-d",
altInput: true,
altFormat: "F j, Y",
onChange: function () {
filterAndRenderData(data);
},
});
// Set up category dropdown change listener
const categoryFilter = document.getElementById('category-filter');
if (categoryFilter) {
categoryFilter.onchange = () => filterAndRenderData(data);
}
}
// Initialize dashboard after filters are set
function initializeDashboard(data) {
populateCategoryFilter(data); // Populate category dropdown
filterAndRenderData(data); // Initial render with all data
}
// Apply filters and update key metrics, chart, and table
function filterAndRenderData(data) {
const chartType = document.getElementById('chart-type-selector').value;
const startDate = document.getElementById('start-date')._flatpickr.selectedDates[0];
const endDate = document.getElementById('end-date')._flatpickr.selectedDates[0];
const selectedCategory = document.getElementById('category-filter').value;
// Filter data by date and category
const filteredData = data.filter((item) => {
const itemDate = new Date(item.order_date.split('/').reverse().join('-'));
return (
itemDate >= startDate &&
itemDate <= endDate &&
(selectedCategory === 'all' || item.categories === selectedCategory)
);
});
updateKeyMetrics(filteredData); // Update metrics like revenue and orders
drawChart(filteredData, 'chart-canvas', chartType); // Render chart
populateDataTable(filteredData); // Update table
}
// Update dashboard metrics (total revenue, order count, etc.)
function updateKeyMetrics(data) {
const totalRevenue = data.reduce((acc, item) => acc + parseFloat(item.total), 0);
const totalOrders = data.length;
const averageOrderValue = totalOrders > 0 ? totalRevenue / totalOrders : 0;
// Calculate total revenue per category to find top category
const revenueByCategory = data.reduce((acc, item) => {
const category = item.categories || "Uncategorized";
acc[category] = (acc[category] || 0) + parseFloat(item.total);
return acc;
}, {});
// Determine category with highest total revenue
const topCategory = Object.keys(revenueByCategory).reduce(
(a, b) => (revenueByCategory[a] > revenueByCategory[b] ? a : b),
"None"
);
// Display metrics in the DOM
document.getElementById('total-revenue').textContent = `$${totalRevenue.toFixed(2)}`;
document.getElementById('total-orders').textContent = `${totalOrders}`;
document.getElementById('average-order-value').textContent = `$${averageOrderValue.toFixed(2)}`;
document.getElementById('top-category').textContent = topCategory || 'None';
}
// Draw the selected chart type using Chart.js
function drawChart(data, elementId, chartType) {
const ctx = document.getElementById(elementId).getContext('2d');
// Destroy previous chart if one exists
if (chartInstance) {
chartInstance.destroy();
}
switch (chartType) {
case 'revenueOverTime':
// Line chart showing revenue by order date
chartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: data.map((item) => item.order_date),
datasets: [{
label: 'Revenue Over Time',
data: data.map((item) => parseFloat(item.total)),
fill: false,
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
}],
},
options: {
scales: {
y: { beginAtZero: true },
},
},
});
break;
case 'revenueByCategory':
// Bar chart showing total revenue per category
const categories = [...new Set(data.map((item) => item.categories))];
const revenueByCategory = categories.map((category) => {
return {
category,
revenue: data
.filter((item) => item.categories === category)
.reduce((acc, item) => acc + parseFloat(item.total), 0),
};
});
chartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: revenueByCategory.map((item) => item.category),
datasets: [{
label: 'Revenue by Category',
data: revenueByCategory.map((item) => item.revenue),
backgroundColor: 'rgba(255, 99, 132, 0.2)',
borderColor: 'rgba(255, 99, 132, 1)',
borderWidth: 1,
}],
},
options: {
scales: {
y: { beginAtZero: true },
},
},
});
break;
case 'topProducts':
// Horizontal bar chart showing top 10 products by revenue
const productRevenue = data.reduce((acc, item) => {
const productName = item.product_names || 'Unknown Product';
acc[productName] = (acc[productName] || 0) + parseFloat(item.total);
return acc;
}, {});
const topProducts = Object.entries(productRevenue)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
chartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: topProducts.map((item) => item[0]), // Product names
datasets: [{
label: 'Top Products by Revenue',
data: topProducts.map((item) => item[1]), // Revenue
backgroundColor: 'rgba(54, 162, 235, 0.8)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1,
}],
},
options: {
indexAxis: 'y', // Horizontal bars
scales: {
x: { beginAtZero: true },
},
},
});
break;
}
}
// Display filtered data in a DataTable
function populateDataTable(data) {
const tableElement = $('#data-table');
// Destroy existing table if it exists
if ($.fn.DataTable.isDataTable(tableElement)) {
tableElement.DataTable().clear().destroy();
}
// Create a new DataTable with relevant columns
tableElement.DataTable({
data: data.map((item) => [
item.order_id,
item.order_date,
item.customer_id,
item.product_names,
item.categories,
`$${parseFloat(item.total).toFixed(2)}`,
]),
columns: [
{ title: "Order ID" },
{ title: "Order Date" },
{ title: "Customer ID" },
{ title: "Product" },
{ title: "Category" },
{ title: "Total" },
],
});
}
// Populate the category filter dropdown with available categories
function populateCategoryFilter(data) {
const categoryFilter = document.getElementById('category-filter');
categoryFilter.innerHTML = '';
categoryFilter.appendChild(new Option('All Categories', 'all', true, true));
// Extract unique categories
const categories = new Set(data.map((item) => item.categories));
categories.forEach((category) => {
categoryFilter.appendChild(new Option(category, category));
});
}
这是我们的最复杂的代码文件,但它必须做很多事情。此 JavaScript 文件为销售绩效仪表板的交互性和数据可视化提供动力。简而言之,它 …
1/ 获取销售数据
-
当页面加载时 (
DOMContentLoaded),它会在 /data 端点调用后端 API。 -
如果没有返回数据,则显示“没有可用数据”消息。
2/ 设置过滤器
-
使用 Flatpickr 日期选择器根据数据集的最小/最大订单日期选择开始和结束日期。
-
添加一个 类别下拉菜单,允许用户按产品类别进行筛选。
-
添加一个 图表类型选择器,以便在不同的图表可视化之间切换。
3/ 初始化仪表板
-
用可用的类别填充类别过滤器。
-
使用完整数据集运行第一次渲染。
4/ 应用过滤器并重新渲染
-
每次用户更改过滤器(日期范围、类别或图表类型)时,它:
-
通过日期范围和类别过滤数据集。
-
更新 关键指标:总收入、订单数量、平均订单价值和最高收入类别。
-
重新绘制选定的 Chart.js 图表。
-
刷新 数据表。
-
5/ 使用 Chart.js 绘制图表
-
随时间推移的收入 → 按日期显示收入趋势的折线图。
-
按类别划分的收入 → 按类别汇总的总收入条形图。
-
顶级产品 → 水平条形图显示按收入排名前 10 的产品。
6/ 显示表格数据
- 使用 DataTables (一个 jQuery 插件) 渲染一个过滤订单的表格,包含订单 ID、日期、客户 ID、产品、类别和总计列。
7/ 保持 UI 同步
-
当过滤器改变时销毁并重新创建图表/表格,以避免重复。
-
保持指标、图表和表格与活动过滤器的一致性。
运行我们的仪表板
现在我们已经整理好了所有代码,是时候运行仪表板了,所以请转到服务器子文件夹,并输入以下命令。
$ node server.js
您将收到上述命令的响应,类似于,
Server running at http://localhost:3000
打开网页浏览器并访问http://localhost:3000。您应该会看到您的仪表板已用 SQLite 数据库中的数据填充,如下面的图片所示。

图片由作者提供
所有过滤器、图表选择等都应该按预期工作。
摘要
在这篇文章中,我向您介绍了如何使用核心网络技术——HTML、CSS、JavaScript、Node.js、Express 和本地 SQLite 数据库创建一个功能齐全、交互式的销售绩效仪表板。
我们讨论了技术栈和设置。即:
-
后端: Node.js,Express,SQLite
-
前端: HTML,Bootstrap(用于布局),Chart.js(用于图表),Flatpickr(日期选择器),DataTables(用于表格数据)
-
如下所示的文件夹结构。
my-dashboard/
├── client/
│ ├── index.html
│ ├── style.css
│ └── script.js
└── server/
└── server.js
我向您展示了如何通过代码创建并填充一个 SQLite 数据库,我们可以将其用作仪表板的源数据。我们还讨论了环境设置以及前端和后端开发过程,并简要介绍了我们的数据仪表板功能。
最后,我详细介绍了我们需要创建的四个代码文件,并展示了如何在浏览器中运行仪表板。

浙公网安备 33010602011771号