好的,让我们继续改进用户界面和用户体验。

第六部分:改进用户界面和用户体验

  1. 创建一个新的局部视图 src/views/partials/messages.eta 来显示闪现消息:
<% if (it.messages && it.messages.length > 0) { %>
  <div id="messages">
    <% it.messages.forEach(function(message) { %>
      <div class="message <%= message.type %>">
        <%= message.text %>
      </div>
    <% }) %>
  </div>
  <script>
    setTimeout(() => {
      const messages = document.getElementById('messages');
      if (messages) {
        messages.style.opacity = '0';
        setTimeout(() => messages.remove(), 500);
      }
    }, 5000);
  </script>
<% } %>
  1. 更新 src/views/layouts/main.eta 以包含闪现消息:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= it.title %></title>
    <script src="https://unpkg.com/htmx.org@1.9.6"></script>
    <link rel="stylesheet" href="/styles/main.css">
</head>
<body>
    <%~ includeFile('../partials/header.eta', it) %>
    
    <main>
        <%~ includeFile('../partials/messages.eta', it) %>
        <%~ it.body %>
    </main>
    
    <%~ includeFile('../partials/footer.eta') %>
</body>
</html>
  1. 创建一个新的视图 src/views/register.eta
<% layout('./layouts/main.eta', { title: 'Register' }) %>
 
<h1>Register</h1>
<form hx-post="/register" hx-swap="outerHTML">
    <div>
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" required>
    </div>
    <div>
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" required>
    </div>
    <div>
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required>
    </div>
    <button type="submit">Register</button>
</form>
 
<script>
    document.body.addEventListener('htmx:afterRequest', function(event) {
        if (event.detail.successful) {
            window.location.href = '/login';
        } else {
            alert('Registration failed. Please try again.');
        }
    });
</script>
  1. 创建一个新的视图 src/views/create-post.eta
<% layout('./layouts/main.eta', { title: 'Create Post' }) %>
 
<h1>Create New Post</h1>
<form hx-post="/posts" hx-swap="outerHTML">
    <div>
        <label for="title">Title:</label>
        <input type="text" id="title" name="title" required>
    </div>
    <div>
        <label for="content">Content:</label>
        <textarea id="content" name="content" required></textarea>
    </div>
    <button type="submit">Create Post</button>
</form>
 
<script>
    document.body.addEventListener('htmx:afterRequest', function(event) {
        if (event.detail.successful) {
            window.location.href = '/posts/' + JSON.parse(event.detail.xhr.response).id;
        } else {
            alert('Failed to create post. Please try again.');
        }
    });
</script>
  1. 更新 src/views/post.eta 以添加编辑功能:
<% layout('./layouts/main.eta', { title: it.post.title }) %>
 
<article id="post-<%= it.post.id %>">
    <h1><%= it.post.title %></h1>
    <p>By <%= it.post.author %> on <%= new Date(it.post.createdAt).toLocaleDateString() %></p>
    <div><%= it.post.content %></div>
</article>
 
<% if (it.user && it.user.id === it.post.authorId) { %>
    <div>
        <button hx-get="/posts/<%= it.post.id %>/edit" hx-target="#post-<%= it.post.id %>" hx-swap="outerHTML">Edit</button>
        <button hx-delete="/posts/<%= it.post.id %>" hx-confirm="Are you sure you want to delete this post?">Delete</button>
    </div>
<% } %>
 
<h2>Comments</h2>
<div id="comments">
    <!-- 这里可以添加评论列表和评论表单 -->
</div>
 
<script>
    document.body.addEventListener('htmx:afterRequest', function(event) {
        if (event.detail.successful && event.detail.xhr.status === 200) {
            if (event.detail.requestConfig.verb === 'delete') {
                window.location.href = '/';
            }
        } else {
            alert('Operation failed. Please try again.');
        }
    });
</script>
  1. 创建一个新的局部视图 src/views/partials/edit-post.eta
<form hx-put="/posts/<%= it.post.id %>" hx-target="#post-<%= it.post.id %>" hx-swap="outerHTML">
    <div>
        <label for="title">Title:</label>
        <input type="text" id="title" name="title" value="<%= it.post.title %>" required>
    </div>
    <div>
        <label for="content">Content:</label>
        <textarea id="content" name="content" required><%= it.post.content %></textarea>
    </div>
    <button type="submit">Update Post</button>
    <button type="button" hx-get="/posts/<%= it.post.id %>" hx-target="#post-<%= it.post.id %>" hx-swap="outerHTML">Cancel</button>
</form>
  1. 更新 src/index.ts 以添加新的路由:
import { Elysia } from "elysia";
import { html } from "@elysiajs/html";
import { staticPlugin } from "@elysiajs/static";
import { authRoutes, auth } from "./routes/auth";
import { postRoutes } from "./routes/posts";
import { migrate } from "drizzle-orm/libsql/migrator";
import { db } from "./db";
import { renderFile } from "eta";
import path from "path";
import { posts, users } from "./db/schema";
import { eq } from "drizzle-orm";
 
// 运行迁移
await migrate(db, { migrationsFolder: "./drizzle" });
 
const app = new Elysia()
  .use(html())
  .use(staticPlugin())
  .use(auth)
  .use(authRoutes)
  .use(postRoutes)
  .get("/", async ({ set, getCurrentUser }) => {
    const posts = await db.select().from(posts).limit(5).all();
    const user = await getCurrentUser();
    set.headers['Content-Type'] = 'text/html';
    return await renderFile(path.join(__dirname, "views", "home.eta"), { posts, user });
  })
  .get("/login", async ({ set, getCurrentUser }) => {
    const user = await getCurrentUser();
    if (user) {
      set.redirect = "/";
      return;
    }
    set.headers['Content-Type'] = 'text/html';
    return await renderFile(path.join(__dirname, "views", "login.eta"), {});
  })
  .get("/register", async ({ set, getCurrentUser }) => {
    const user = await getCurrentUser();
    if (user) {
      set.redirect = "/";
      return;
    }
    set.headers['Content-Type'] = 'text/html';
    return await renderFile(path.join(__dirname, "views", "register.eta"), {});
  })
  .get("/posts/:id", async ({ params, set, getCurrentUser }) => {
    const post = await db.select().from(posts).where(eq(posts.id, parseInt(params.id))).get();
    if (!post) {
      set.status = 404;
      return "Post not found";
    }
    const author = await db.select().from(users).where(eq(users.id, post.authorId)).get();
    const user = await getCurrentUser();
    set.headers['Content-Type'] = 'text/html';
    return await renderFile(path.join(__dirname, "views", "post.eta"), { post, user, author: author.username });
  })
  .get("/posts/:id/edit", async ({ params, set, getCurrentUser }) => {
    const user = await getCurrentUser();
    if (!user) {
      set.status = 401;
      return "Not authorized";
    }
    const post = await db.select().from(posts).where(eq(posts.id, parseInt(params.id))).get();
    if (!post || post.authorId !== user.id) {
      set.status = 404;
      return "Post not found or not authorized";
    }
    set.headers['Content-Type'] = 'text/html';
    return await renderFile(path.join(__dirname, "views", "partials", "edit-post.eta"), { post });
  })
  .get("/create-post", async ({ set, getCurrentUser }) => {
    const user = await getCurrentUser();
    if (!user) {
      set.redirect = "/login";
      return;
    }
    set.headers['Content-Type'] = 'text/html';
    return await renderFile(path.join(__dirname, "views", "create-post.eta"), { user });
  })
  .listen(3000);
 
console.log(`Server is running at ${app.server?.hostname}:${app.server?.port}`);
  1. 更新 public/styles/main.css 以添加更多样式:
body {
    font-family: Arial, sans-serif;
    line-height: 1.6;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
 
header {
    background-color: #333;
    color: #fff;
    padding: 1rem;
}
 
nav ul {
    list-style-type: none;
    padding: 0;
}
 
nav ul li {
    display: inline;
    margin-right: 10px;
}
 
nav ul li a {
    color: #fff;
    text-decoration: none;
}
 
main {
    flex: 1;
    padding: 2rem;
    max-width: 800px;
    margin: 0 auto;
}
 
footer {
    background-color: #333;
    color: #fff;
    text-align: center;
    padding: 1rem;
    margin-top: auto;
}
 
form {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}
 
input, textarea {
    width: 100%;
    padding: 0.5rem;
}
 
button {
    padding: 0.5rem 1rem;
    background-color: #333;
    color: #fff;
    border: none;
    cursor: pointer;
}
 
button:hover {
    background-color: #444;
}
 
.message {
    padding: 1rem;
    margin-bottom: 1rem;
    border-radius: 4px;
}
 
.message.success {
    background-color: #d4edda;
    color: #155724;
}
 
.message.error {
    background-color: #f8d7da;
    color: #721c24;
}
 
#messages {
    transition: opacity 0.5s ease-in-out;
}

这些更改大大改进了用户界面和用户体验。我们添加了注册页面、创建帖子页面,并改进了帖子编辑功能。我们还添加了闪现消息系统来显示操作结果,并改进了整体样式。

接下来,我们可以添加一些额外的功能,如分页、搜索或用户资料页面。你想继续哪个方向?