非常好的想法!我们将实现数据分析和报告功能,并为标签云添加动画效果。这将帮助我们更好地理解用户行为和内容表现,同时提升用户界面的吸引力。让我们开始吧:

第三十部分:数据分析、报告功能和标签云动画

  1. 首先,我们需要创建一个新的表来存储文章浏览量。更新 src/db/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
 
// ... 保留现有的表定义
 
export const postViews = sqliteTable("post_views", {
  id: integer("id").primaryKey(),
  postId: integer("post_id").notNull().references(() => posts.id),
  viewCount: integer("view_count").notNull().default(0),
  lastViewed: integer("last_viewed", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`),
});
  1. 创建新的迁移文件 src/db/migrations/0014_add_post_views_table.ts
export async function up(db) {
  await db.run(sql`
    CREATE TABLE post_views (
      id INTEGER PRIMARY KEY,
      post_id INTEGER NOT NULL,
      view_count INTEGER NOT NULL DEFAULT 0,
      last_viewed TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (post_id) REFERENCES posts(id)
    );
  `);
}
 
export async function down(db) {
  await db.run(sql`DROP TABLE IF EXISTS post_views;`);
}
  1. 运行迁移:
bun run migrate
  1. 更新 src/routes/posts.ts 文件,记录文章浏览量:
import { db } from "../db";
import { posts, comments, users, postViews } from "../db/schema";
import { eq } from "drizzle-orm";
 
export const postRoutes = new Elysia()
  // ... 保留现有的路由
  .get("/posts/:id", async ({ params, session, render }) => {
    const postId = parseInt(params.id);
    const post = await db.select().from(posts).where(eq(posts.id, postId)).get();
    if (!post) {
      return "Post not found";
    }
 
    // 更新浏览量
    await db.insert(postViews)
      .values({ postId, viewCount: 1 })
      .onConflict({ target: postViews.postId })
      .do(sql`
        UPDATE SET 
          view_count = view_count + 1,
          last_viewed = CURRENT_TIMESTAMP
      `);
 
    // ... 其他代码保持不变
  });
  1. 创建一个新文件 src/routes/analytics.ts 来处理分析相关的路由:
import { Elysia } from "elysia";
import { db } from "../db";
import { posts, postViews, comments, tags } from "../db/schema";
import { sql } from "drizzle-orm";
 
export const analyticsRoutes = new Elysia()
  .get("/analytics", async ({ render }) => {
    const topPosts = await db
      .select({
        id: posts.id,
        title: posts.title,
        viewCount: postViews.viewCount,
      })
      .from(posts)
      .leftJoin(postViews, eq(posts.id, postViews.postId))
      .orderBy(desc(postViews.viewCount))
      .limit(10)
      .all();
 
    const topTags = await db
      .select({
        name: tags.name,
        count: sql<number>`count(*)`.as("count"),
      })
      .from(tags)
      .innerJoin(postTags, eq(tags.id, postTags.tagId))
      .groupBy(tags.name)
      .orderBy(desc(sql`count(*)`))
      .limit(20)
      .all();
 
    const totalPosts = await db.select({ count: sql<number>`count(*)` }).from(posts).get();
    const totalComments = await db.select({ count: sql<number>`count(*)` }).from(comments).get();
 
    return render("analytics", { topPosts, topTags, totalPosts: totalPosts.count, totalComments: totalComments.count });
  });
  1. 创建一个新的视图文件 src/views/analytics.eta
<% layout('./layouts/main.eta', { title: 'Analytics' }) %>
 
<h1>Analytics Dashboard</h1>
 
<div class="dashboard">
  <div class="stat-box">
    <h2>Total Posts</h2>
    <p><%= it.totalPosts %></p>
  </div>
  <div class="stat-box">
    <h2>Total Comments</h2>
    <p><%= it.totalComments %></p>
  </div>
</div>
 
<h2>Top 10 Most Viewed Posts</h2>
<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Views</th>
    </tr>
  </thead>
  <tbody>
    <% it.topPosts.forEach(post => { %>
      <tr>
        <td><a href="/posts/<%= post.id %>"><%= post.title %></a></td>
        <td><%= post.viewCount %></td>
      </tr>
    <% }) %>
  </tbody>
</table>
 
<h2>Top Tags</h2>
<div id="tag-cloud"></div>
 
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
const tags = <%= JSON.stringify(it.topTags) %>;
 
const width = 600;
const height = 400;
 
const svg = d3.select("#tag-cloud")
  .append("svg")
  .attr("width", width)
  .attr("height", height);
 
const fontScale = d3.scaleLinear()
  .domain([d3.min(tags, d => d.count), d3.max(tags, d => d.count)])
  .range([14, 50]);
 
const simulation = d3.forceSimulation(tags)
  .force("center", d3.forceCenter(width / 2, height / 2))
  .force("collision", d3.forceCollide().radius(d => fontScale(d.count) / 2))
  .force("x", d3.forceX(width / 2).strength(0.1))
  .force("y", d3.forceY(height / 2).strength(0.1));
 
const nodes = svg.selectAll("text")
  .data(tags)
  .enter().append("text")
  .text(d => d.name)
  .attr("font-size", d => `${fontScale(d.count)}px`)
  .attr("fill", () => d3.interpolateRainbow(Math.random()))
  .style("cursor", "pointer")
  .on("click", (event, d) => {
    window.location.href = `/tags/${d.name}`;
  });
 
simulation.on("tick", () => {
  nodes.attr("x", d => d.x)
    .attr("y", d => d.y);
});
 
// 添加动画效果
function animateTags() {
  nodes.transition()
    .duration(1000)
    .attr("font-size", d => `${fontScale(d.count) * (0.8 + Math.random() * 0.4)}px`)
    .attr("fill", () => d3.interpolateRainbow(Math.random()));
}
 
setInterval(animateTags, 3000);
</script>
 
<style>
.dashboard {
  display: flex;
  justify-content: space-around;
  margin-bottom: 2em;
}
 
.stat-box {
  background-color: #f0f0f0;
  padding: 1em;
  border-radius: 5px;
  text-align: center;
}
 
table {
  width: 100%;
  border-collapse: collapse;
}
 
th, td {
  border: 1px solid #ddd;
  padding: 8px;
  text-align: left;
}
 
th {
  background-color: #f2f2f2;
}
 
#tag-cloud {
  width: 600px;
  height: 400px;
  margin: 0 auto;
}
</style>
  1. 更新 src/index.ts 文件,添加新的分析路由:
import { Elysia } from "elysia";
import { analyticsRoutes } from "./routes/analytics";
 
const app = new Elysia()
  // ... 其他中间件和路由
  .use(analyticsRoutes)
  .listen(3000);
 
console.log(`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`);
  1. src/views/layouts/main.eta 中添加一个链接到分析页面:
<nav>
  <!-- ... 其他导航链接 ... -->
  <a href="/analytics">Analytics</a>
</nav>

这些更改实现了以下功能:

  1. 记录和展示文章浏览量。
  2. 创建了一个分析仪表板,显示总文章数、总评论数、最受欢迎的文章和标签。
  3. 使用 D 3. Js 创建了一个动态的、可交互的标签云。
  4. 为标签云添加了动画效果,每隔几秒钟就会改变标签的大小和颜色。

主要改进包括:

  • 提供了对博客性能的深入洞察。
  • 可视化了最受欢迎的内容和主题。
  • 增加了用户界面的吸引力,使标签云更加生动有趣。
  • 通过可点击的标签云提高了用户参与度。

这个实现为博客系统添加了强大的分析功能,同时提升了用户界面的视觉吸引力。管理员现在可以更好地了解用户行为和内容表现,而用户则可以通过生动的标签云更轻松地探索博客内容。

接下来,我们可以考虑以下方向来进一步完善系统:

  1. 添加更多的分析图表,如用户增长趋势、评论活跃度等。
  2. 实现自定义日期范围的数据分析。
  3. 添加用户行为跟踪,如页面停留时间、点击路径等。
  4. 实现数据导出功能,允许管理员下载 CSV 或 Excel 格式的报告。
  5. 添加实时分析功能,使用 WebSocket 实时更新仪表板。

你希望继续哪个方向,或者有其他想法吗?