好的,让我们继续改进用户界面和用户体验。
第六部分:改进用户界面和用户体验
- 创建一个新的局部视图
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>
<% } %>- 更新
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>- 创建一个新的视图
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>- 创建一个新的视图
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>- 更新
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>- 创建一个新的局部视图
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>- 更新
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}`);- 更新
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;
}这些更改大大改进了用户界面和用户体验。我们添加了注册页面、创建帖子页面,并改进了帖子编辑功能。我们还添加了闪现消息系统来显示操作结果,并改进了整体样式。
接下来,我们可以添加一些额外的功能,如分页、搜索或用户资料页面。你想继续哪个方向?