当然可以。我们可以使用 UnoCSS 来实现主题和布局的自定义功能,并重构项目以支持模板功能。这将大大提高用户界面的灵活性和可定制性。让我们来重构这个项目:
- 首先,安装必要的依赖:
bun add unocss @unocss/preset-uno @unocss/preset-attributify @unocss/preset-typography- 创建 UnoCSS 配置文件
在项目根目录创建 uno.config.ts:
import { defineConfig } from 'unocss'
import presetUno from '@unocss/preset-uno'
import presetAttributify from '@unocss/preset-attributify'
import presetTypography from '@unocss/preset-typography'
export default defineConfig({
presets: [
presetUno(),
presetAttributify(),
presetTypography(),
],
theme: {
colors: {
primary: {
50: '#f0f9ff',
100: '#e0f2fe',
// ... 其他色阶
900: '#0c4a6e',
},
// 可以添加其他颜色
},
},
})- 更新
src/index.ts以集成 UnoCSS
import { Elysia } from 'elysia'
import { html } from '@elysiajs/html'
import { staticPlugin } from '@elysiajs/static'
import { createGenerator } from '@unocss/core'
import presetUno from '@unocss/preset-uno'
import presetAttributify from '@unocss/preset-attributify'
import presetTypography from '@unocss/preset-typography'
// ... 其他导入
const uno = createGenerator({
presets: [
presetUno(),
presetAttributify(),
presetTypography(),
],
})
const app = new Elysia()
.use(html())
.use(staticPlugin())
// ... 其他中间件
.get('/uno.css', async () => {
const { css } = await uno.generate('')
return new Response(css, {
headers: {
'Content-Type': 'text/css',
},
})
})
// ... 其他路由
.listen(3000)- 创建主题和布局系统
创建 src/themes 目录,并添加一些主题文件:
/src
/themes
default.ts
dark.ts
colorful.ts
例如,default.ts 可能如下所示:
export default {
name: 'Default',
colors: {
background: 'bg-white',
text: 'text-gray-800',
primary: 'text-blue-600',
secondary: 'text-gray-600',
},
layout: {
container: 'max-w-4xl mx-auto px-4',
header: 'py-6',
main: 'py-8',
footer: 'py-6 text-center',
},
}- 更新 React 组件以使用主题
更新 src/components/Blog.tsx:
import React from 'react'
import { Theme } from '../types'
interface BlogProps {
posts: Post[]
user: User | null
theme: Theme
}
export const Blog: React.FC<BlogProps> = ({ posts, user, theme }) => {
return (
<div className={`${theme.colors.background} ${theme.colors.text}`}>
<header className={theme.layout.header}>
<div className={theme.layout.container}>
<h1 className={`text-3xl font-bold ${theme.colors.primary}`}>My Blog</h1>
<nav className="mt-4">
<a href="/" className={`mr-4 ${theme.colors.primary}`}>Home</a>
<a href="/stats" className={`mr-4 ${theme.colors.primary}`}>Statistics</a>
{user ? (
<span>Welcome, {user.name}! <a href="/logout" className={theme.colors.secondary}>Logout</a></span>
) : (
<a href="/login" className={theme.colors.primary}>Login</a>
)}
</nav>
</div>
</header>
<main className={`${theme.layout.main} ${theme.layout.container}`}>
{/* 文章列表和其他内容 */}
</main>
<footer className={`${theme.layout.footer} ${theme.colors.secondary}`}>
<div className={theme.layout.container}>
© 2023 My Blog. All rights reserved.
</div>
</footer>
</div>
)
}- 实现主题选择功能
创建 src/components/ThemeSelector.tsx:
import React from 'react'
import { Theme } from '../types'
interface ThemeSelectorProps {
themes: Theme[]
currentTheme: string
onThemeChange: (themeName: string) => void
}
export const ThemeSelector: React.FC<ThemeSelectorProps> = ({ themes, currentTheme, onThemeChange }) => {
return (
<select
value={currentTheme}
onChange={(e) => onThemeChange(e.target.value)}
className="p-2 border rounded"
>
{themes.map((theme) => (
<option key={theme.name} value={theme.name}>
{theme.name}
</option>
))}
</select>
)
}- 更新
src/index.ts以支持主题切换
import { Theme } from './types'
import defaultTheme from './themes/default'
import darkTheme from './themes/dark'
import colorfulTheme from './themes/colorful'
const themes: Record<string, Theme> = {
default: defaultTheme,
dark: darkTheme,
colorful: colorfulTheme,
}
app.get('/', async ({ cookie, html, set }) => {
const currentTheme = cookie.theme || 'default'
const theme = themes[currentTheme]
const blogHtml = renderToString(
<Blog posts={posts} user={user} theme={theme} />
)
return html(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Blog</title>
<link rel="stylesheet" href="/uno.css">
<script src="https://unpkg.com/htmx.org@1.9.0"></script>
</head>
<body>
<div id="root">${blogHtml}</div>
<script>
function changeTheme(theme) {
document.cookie = 'theme=' + theme + ';path=/';
window.location.reload();
}
</script>
</body>
</html>
`)
})
app.post('/change-theme', ({ body, set }) => {
const { theme } = body as { theme: string }
set.cookie('theme', theme, { path: '/' })
set.redirect = '/'
})- 添加主题选择器到 Blog 组件
更新 src/components/Blog.tsx:
import React from 'react'
import { Theme } from '../types'
import { ThemeSelector } from './ThemeSelector'
interface BlogProps {
posts: Post[]
user: User | null
theme: Theme
availableThemes: string[]
}
export const Blog: React.FC<BlogProps> = ({ posts, user, theme, availableThemes }) => {
return (
<div className={`${theme.colors.background} ${theme.colors.text}`}>
{/* ... 其他内容 */}
<div className="mt-4">
<form action="/change-theme" method="post">
<ThemeSelector
themes={availableThemes.map(t => ({ name: t }))}
currentTheme={theme.name}
onThemeChange={(themeName) => {
const form = document.createElement('form')
form.method = 'post'
form.action = '/change-theme'
const input = document.createElement('input')
input.type = 'hidden'
input.name = 'theme'
input.value = themeName
form.appendChild(input)
document.body.appendChild(form)
form.submit()
}}
/>
</form>
</div>
{/* ... 其他内容 */}
</div>
)
}- 更新 Dockerfile 以包含 UnoCSS 生成
更新 Dockerfile:
# ... 前面的步骤保持不变
# 生成 UnoCSS
RUN bun run unocss
# ... 后面的步骤保持不变- 更新
package.json脚本
{
"scripts": {
"start": "bun run src/index.ts",
"dev": "bun run --watch src/index.ts",
"unocss": "unocss src/**/*.tsx -o public/uno.css"
}
}这个重构后的项目现在支持以下新功能:
- 使用 UnoCSS 进行样式管理,提供更灵活的主题定制能力。
- 实现了主题系统,允许用户在不同的预定义主题之间切换。
- 布局现在是可配置的,可以为不同的主题定义不同的布局。
- 主题选择器允许用户轻松切换主题。
注意事项:
- 确保在开发过程中运行
bun run unocss来生成最新的 CSS 文件。 - 在添加新的 UI 组件时,记得使用主题中定义的类名和颜色。
- 考虑添加自定义主题功能,允许用户创建和保存自己的主题。
- 可以进一步优化性能,例如使用服务端渲染 (SSR) 或增量静态再生成 (ISR)。
- 考虑添加动画和过渡效果以增强用户体验。
这个重构提供了一个更加灵活和可定制的用户界面,同时保持了原有的功能。你可以基于这个基础继续扩展,添加更多的主题选项,或者实现更高级的布局定制功能。