好的,我们可以扩展这个程序来支持自定义主题 (theme)功能。我们将添加以下功能:
- 用户可以创建、编辑和选择不同的主题模板
- 主题模板将被保存到文件系统中
- 用户可以在界面上切换不同的主题
让我们开始修改和添加一些文件:
- 首先, 创建一个新的文件
src/themes.ts来处理主题相关的功能:
import { readFile, writeFile, readdir } from 'fs/promises';
import path from 'path';
const THEMES_DIR = path.join(process.cwd(), 'themes');
export interface Theme {
name: string;
content: string;
}
export async function getThemes(): Promise<Theme[]> {
const files = await readdir(THEMES_DIR);
const themes = await Promise.all(
files.map(async (file) => {
const content = await readFile(path.join(THEMES_DIR, file), 'utf-8');
return { name: path.parse(file).name, content };
})
);
return themes;
}
export async function saveTheme(name: string, content: string): Promise<void> {
await writeFile(path.join(THEMES_DIR, `${name}.html`), content);
}
export async function getTheme(name: string): Promise<Theme | null> {
try {
const content = await readFile(path.join(THEMES_DIR, `${name}.html`), 'utf-8');
return { name, content };
} catch (error) {
return null;
}
}- 修改
src/index.ts文件, 添加新的路由来处理主题:
import { Elysia } from "elysia";
import { html } from "@elysiajs/html";
import { staticPlugin } from "@elysiajs/static";
import { layout } from "./views/layout";
import { forecast } from "./views/forecast";
import { getWeatherData } from "./weather";
import { getThemes, saveTheme, getTheme } from "./themes";
const app = new Elysia()
.use(html())
.use(staticPlugin())
.get("/", ({ html }) => html(layout(forecast())))
.get("/forecast/:city/:days", async ({ params, html }) => {
const { city, days } = params;
const weatherData = await getWeatherData(city, parseInt(days));
return html(forecast(city, parseInt(days), weatherData));
})
.get("/themes", async ({ html }) => {
const themes = await getThemes();
return html(layout(await import("./views/themes").then(m => m.themesList(themes))));
})
.get("/themes/edit/:name?", async ({ params, html }) => {
const theme = params.name ? await getTheme(params.name) : null;
return html(layout(await import("./views/themes").then(m => m.themeEditor(theme))));
})
.post("/themes/save", async ({ body, html }) => {
const { name, content } = body as { name: string; content: string };
await saveTheme(name, content);
return html(`<div class="text-green-500">Theme saved successfully!</div>`);
})
.get("/apply-theme/:name", async ({ params, html }) => {
const theme = await getTheme(params.name);
if (theme) {
return html(theme.content);
}
return html(`<div class="text-red-500">Theme not found</div>`);
})
.listen(3000);
console.log(`Server is running at http://localhost:${app.server?.port}`);- 创建一个新文件
src/views/themes.ts来处理主题列表和编辑器的视图:
import { Theme } from "../themes";
export const themesList = (themes: Theme[]) => `
<div class="mb-4">
<h2 class="text-2xl mb-2">Themes</h2>
<ul>
${themes.map(theme => `
<li class="mb-2">
${theme.name}
<button hx-get="/apply-theme/${theme.name}" hx-target="body" class="ml-2 p-1 bg-blue-500 text-white rounded">Apply</button>
<a href="/themes/edit/${theme.name}" class="ml-2 p-1 bg-green-500 text-white rounded">Edit</a>
</li>
`).join('')}
</ul>
<a href="/themes/edit" class="mt-4 inline-block p-2 bg-purple-500 text-white rounded">Create New Theme</a>
</div>
`;
export const themeEditor = (theme: Theme | null) => `
<div class="mb-4">
<h2 class="text-2xl mb-2">${theme ? 'Edit Theme' : 'Create New Theme'}</h2>
<form hx-post="/themes/save" hx-swap="outerHTML">
<div class="mb-2">
<label for="name" class="block">Theme Name:</label>
<input type="text" id="name" name="name" value="${theme?.name || ''}" required class="w-full p-2 border rounded">
</div>
<div class="mb-2">
<label for="content" class="block">Theme Content:</label>
<textarea id="content" name="content" rows="10" required class="w-full p-2 border rounded">${theme?.content || ''}</textarea>
</div>
<button type="submit" class="p-2 bg-blue-500 text-white rounded">Save Theme</button>
</form>
</div>
`;- 修改
src/views/layout.ts文件, 添加一个链接到主题列表:
export const layout = (content: string) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Forecast</title>
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<link href="/public/styles.css" rel="stylesheet">
</head>
<body class="bg-gray-100 text-gray-800">
<div class="container mx-auto p-4">
<h1 class="text-3xl font-bold mb-4">Weather Forecast</h1>
<nav class="mb-4">
<a href="/" class="mr-4">Home</a>
<a href="/themes" class="mr-4">Themes</a>
</nav>
${content}
</div>
</body>
</html>
`;- 在项目根目录下创建一个
themes文件夹, 并添加一个默认主题default.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Forecast - Default Theme</title>
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<style>
body { font-family: Arial, sans-serif; background-color: #f0f0f0; }
.container { max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.forecast { background-color: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
</style>
</head>
<body>
<div class="container">
<h1>Weather Forecast</h1>
<div id="content"></div>
</div>
<script>
htmx.ajax('GET', '/', '#content');
</script>
</body>
</html>现在, 你的应用支持自定义主题了。用户可以:
- 查看现有的主题列表 (
/themes) - 创建新主题或编辑现有主题 (
/themes/edit或/themes/edit/:name) - 应用不同的主题 (点击主题列表中的 “Apply” 按钮)
要运行这个更新后的应用:
- 确保你已经创建了
themes文件夹并添加了default.html - 运行
bun run dev - 访问
http://localhost:3000
这个实现允许用户创建和编辑完整的 HTML 模板作为主题。在实际应用中, 你可能想要限制用户只能编辑特定的 CSS 样式或模板部分, 以确保应用的核心功能不会被破坏。此外, 你可能还想添加用户认证和授权, 以控制谁可以编辑和应用主题。