当然可以。我会为您创建一个在 Bun 环境下使用 HTMX 和 UnoCSS 的完整案例,包括创建一个复杂的表格,其中包含图形显示和 Markdown 编辑功能。这个例子会涉及到服务器端和客户端的代码。
首先,让我们设置项目结构:
project/
├── src/
│ ├── index.ts
│ ├── routes/
│ │ └── table.ts
│ └── views/
│ ├── layout.ts
│ └── table.ts
├── public/
│ └── scripts/
│ ├── marked.min.js
│ └── chart.min.js
├── package.json
└── tsconfig.json
现在,让我们逐步实现各个部分:
- 安装依赖:
bun add hono @hono/node-server htmx.org unocss marked chart.js
bun add -d @types/marked @types/chart.js- 配置
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}- 创建主服务器文件
src/index.ts:
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { serveStatic } from "hono/serve-static.bun";
import { tableRoutes } from "./routes/table";
import { layout } from "./views/layout";
const app = new Hono();
app.use("/public/*", serveStatic({ root: "./" }));
app.route("/", tableRoutes);
app.get("*", (c) => c.html(layout("404 Not Found", "<h1>404 Not Found</h1>")));
serve(app);
console.log("Server is running on http://localhost:3000");- 创建表格路由
src/routes/table.ts:
import { Hono } from "hono";
import { tableView } from "../views/table";
const app = new Hono();
type TableData = {
id: number;
name: string;
value: number;
description: string;
};
const tableData: TableData[] = [
{ id: 1, name: "Item 1", value: 100, description: "This is item 1" },
{ id: 2, name: "Item 2", value: 200, description: "This is item 2" },
{ id: 3, name: "Item 3", value: 300, description: "This is item 3" },
];
app.get("/", (c) => c.html(tableView(tableData)));
app.post("/update-cell", async (c) => {
const { id, field, value } = await c.req.parseBody();
const item = tableData.find((item) => item.id === parseInt(id as string));
if (item && field in item) {
(item as any)[field] = value;
return c.html(value as string);
}
return c.text("Error updating cell", 400);
});
app.get("/chart-data", (c) => {
const chartData = {
labels: tableData.map((item) => item.name),
datasets: [
{
label: "Values",
data: tableData.map((item) => item.value),
backgroundColor: "rgba(75, 192, 192, 0.6)",
},
],
};
return c.json(chartData);
});
export { app as tableRoutes };- 创建布局文件
src/views/layout.ts:
export const layout = (title: string, content: string) => `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${title}</title>
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<script src="https://unpkg.com/hyperscript.org@0.9.11"></script>
<script src="https://cdn.jsdelivr.net/npm/@unocss/runtime"></script>
<script src="/public/scripts/marked.min.js"></script>
<script src="/public/scripts/chart.min.js"></script>
<script>
htmx.onLoad(function(content) {
Un.attach(content); // Attach UnoCSS to dynamically loaded content
});
</script>
</head>
<body>
<div class="container mx-auto p-4">
${content}
</div>
</body>
</html>
`;- 创建表格视图
src/views/table.ts:
import { html } from "hono/html";
type TableData = {
id: number;
name: string;
value: number;
description: string;
};
export const tableView = (data: TableData[]) => html`
<h1 class="text-2xl font-bold mb-4">Complex Table Example</h1>
<table class="w-full border-collapse border border-gray-300">
<thead>
<tr class="bg-gray-100">
<th class="border border-gray-300 p-2">ID</th>
<th class="border border-gray-300 p-2">Name</th>
<th class="border border-gray-300 p-2">Value</th>
<th class="border border-gray-300 p-2">Description</th>
<th class="border border-gray-300 p-2">Chart</th>
</tr>
</thead>
<tbody>
${data.map(
(item) => html`
<tr>
<td class="border border-gray-300 p-2">${item.id}</td>
<td class="border border-gray-300 p-2">${item.name}</td>
<td class="border border-gray-300 p-2">
<span
hx-target="this"
hx-swap="outerHTML"
hx-trigger="dblclick"
hx-get="/edit-value?id=${item.id}&value=${item.value}"
>
${item.value}
</span>
</td>
<td class="border border-gray-300 p-2">
<div
class="markdown-content"
hx-target="this"
hx-swap="outerHTML"
hx-trigger="dblclick"
hx-get="/edit-description?id=${item.id}"
>
${item.description}
</div>
</td>
<td class="border border-gray-300 p-2">
<canvas
id="chart-${item.id}"
width="100"
height="50"
hx-trigger="load"
hx-get="/chart-data"
hx-swap="none"
_="on htmx:afterOnLoad
set data to event.detail.xhr.response
call renderChart(#chart-${item.id}, data)"
></canvas>
</td>
</tr>
`
)}
</tbody>
</table>
<script>
function renderChart(canvas, data) {
new Chart(canvas, {
type: 'bar',
data: JSON.parse(data),
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
document.body.addEventListener('htmx:afterSwap', function(event) {
if (event.detail.target.classList.contains('markdown-content')) {
event.detail.target.innerHTML = marked.parse(event.detail.target.textContent);
}
});
</script>
`;- 添加编辑值和描述的路由处理程序到
src/routes/table.ts:
app.get("/edit-value", (c) => {
const id = c.req.query("id");
const value = c.req.query("value");
return c.html(`
<input
type="number"
value="${value}"
hx-post="/update-cell"
hx-trigger="blur"
hx-target="this"
hx-swap="outerHTML"
name="value"
_="on htmx:afterRequest remove me"
>
<input type="hidden" name="id" value="${id}">
<input type="hidden" name="field" value="value">
`);
});
app.get("/edit-description", (c) => {
const id = c.req.query("id");
const item = tableData.find((item) => item.id === parseInt(id as string));
if (!item) return c.text("Item not found", 404);
return c.html(`
<textarea
hx-post="/update-cell"
hx-trigger="blur"
hx-target="this"
hx-swap="outerHTML"
name="description"
_="on htmx:afterRequest remove me"
>${item.description}</textarea>
<input type="hidden" name="id" value="${id}">
<input type="hidden" name="field" value="description">
`);
});- 在
public/scripts/目录下添加marked.min.js和chart.min.js文件。
这个完整的案例展示了如何在 Bun 环境下使用 HTMX 和 UnoCSS 创建一个复杂的表格,包括以下功能:
- 使用 HTMX 进行局部更新和动态加载内容。
- 在单元格中显示图表(使用 Chart. Js)。
- 允许编辑数值和 Markdown 格式的描述。
- 使用 UnoCSS 进行样式设置。
要运行这个项目,请确保您已经安装了 Bun,然后在项目根目录下运行:
bun run src/index.ts这个例子应该能够帮助您理解如何在 Bun 环境下使用 HTMX 和 UnoCSS 创建复杂的交互式表格。您可以根据需要进一步扩展和定制这个示例。