16 changed files with 229 additions and 117 deletions
Binary file not shown.
@ -1,101 +0,0 @@ |
|||
<!DOCTYPE html> |
|||
<html lang="zh-cn"> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|||
<title>登录 / 注册</title> |
|||
<style> |
|||
body { background: #f5f6fa; font-family: 'Segoe UI', Arial, sans-serif; } |
|||
.container { max-width: 350px; margin: 60px auto; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px #0001; padding: 32px 28px; } |
|||
h2 { text-align: center; margin-bottom: 24px; color: #333; } |
|||
.tabs { display: flex; margin-bottom: 24px; } |
|||
.tab { flex: 1; text-align: center; padding: 10px 0; cursor: pointer; border-bottom: 2px solid #eee; color: #888; font-weight: 500; } |
|||
.tab.active { color: #1976d2; border-bottom: 2px solid #1976d2; } |
|||
.form-group { margin-bottom: 18px; } |
|||
label { display: block; margin-bottom: 6px; color: #555; } |
|||
input { width: 100%; padding: 8px 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 15px; } |
|||
button { width: 100%; padding: 10px; background: #1976d2; color: #fff; border: none; border-radius: 4px; font-size: 16px; cursor: pointer; margin-top: 8px; } |
|||
button:active { background: #145ea8; } |
|||
.msg { text-align: center; color: #e53935; margin-bottom: 10px; min-height: 22px; } |
|||
</style> |
|||
</head> |
|||
<body> |
|||
<div class="container"> |
|||
<div class="tabs"> |
|||
<div class="tab active" id="loginTab">登录</div> |
|||
<div class="tab" id="registerTab">注册</div> |
|||
</div> |
|||
<div class="msg" id="msg"></div> |
|||
<form id="loginForm"> |
|||
<div class="form-group"> |
|||
<label for="login-username">用户名</label> |
|||
<input type="text" id="login-username" required autocomplete="username"> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="login-password">密码</label> |
|||
<input type="password" id="login-password" required autocomplete="current-password"> |
|||
</div> |
|||
<button type="submit">登录</button> |
|||
</form> |
|||
<form id="registerForm" style="display:none;"> |
|||
<div class="form-group"> |
|||
<label for="register-username">用户名</label> |
|||
<input type="text" id="register-username" required autocomplete="username"> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="register-email">邮箱</label> |
|||
<input type="email" id="register-email" required autocomplete="email"> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label for="register-password">密码</label> |
|||
<input type="password" id="register-password" required autocomplete="new-password"> |
|||
</div> |
|||
<button type="submit">注册</button> |
|||
</form> |
|||
</div> |
|||
<script> |
|||
const loginTab = document.getElementById('loginTab'); |
|||
const registerTab = document.getElementById('registerTab'); |
|||
const loginForm = document.getElementById('loginForm'); |
|||
const registerForm = document.getElementById('registerForm'); |
|||
const msg = document.getElementById('msg'); |
|||
|
|||
loginTab.onclick = () => { |
|||
loginTab.classList.add('active'); |
|||
registerTab.classList.remove('active'); |
|||
loginForm.style.display = ''; |
|||
registerForm.style.display = 'none'; |
|||
msg.textContent = ''; |
|||
}; |
|||
registerTab.onclick = () => { |
|||
registerTab.classList.add('active'); |
|||
loginTab.classList.remove('active'); |
|||
registerForm.style.display = ''; |
|||
loginForm.style.display = 'none'; |
|||
msg.textContent = ''; |
|||
}; |
|||
|
|||
loginForm.onsubmit = async e => { |
|||
e.preventDefault(); |
|||
msg.textContent = ''; |
|||
const username = document.getElementById('login-username').value.trim(); |
|||
const password = document.getElementById('login-password').value; |
|||
if (!username || !password) { msg.textContent = '请填写完整信息'; return; } |
|||
// TODO: 替换为实际API |
|||
msg.style.color = '#1976d2'; |
|||
msg.textContent = '登录成功(示例)'; |
|||
}; |
|||
registerForm.onsubmit = async e => { |
|||
e.preventDefault(); |
|||
msg.textContent = ''; |
|||
const username = document.getElementById('register-username').value.trim(); |
|||
const email = document.getElementById('register-email').value.trim(); |
|||
const password = document.getElementById('register-password').value; |
|||
if (!username || !email || !password) { msg.textContent = '请填写完整信息'; return; } |
|||
// TODO: 替换为实际API |
|||
msg.style.color = '#1976d2'; |
|||
msg.textContent = '注册成功(示例)'; |
|||
}; |
|||
</script> |
|||
</body> |
|||
</html> |
@ -0,0 +1 @@ |
|||
asd |
@ -0,0 +1,23 @@ |
|||
export const Index = async ctx => { |
|||
return await ctx.render("index", { name: "bluescurry" }) |
|||
} |
|||
|
|||
export const Page = (name, data) => async ctx => { |
|||
return await ctx.render(name, data) |
|||
} |
|||
|
|||
import Router from "utils/router.js" |
|||
export function createRoutes() { |
|||
const router = new Router() |
|||
router.post("/clicked", async ctx => { |
|||
ctx.cookies.set("token", "sadas", { |
|||
httpOnly: true, |
|||
// Setting httpOnly to false allows JavaScript to access the cookie
|
|||
// This enables browsers to automatically include the cookie in requests
|
|||
sameSite: "lax", |
|||
// maxAge: 86400000, // Optional: cookie expiration in milliseconds (e.g., 24 hours)
|
|||
}) |
|||
return await ctx.render("htmx/fuck", { title: "HTMX Clicked" }) |
|||
}) |
|||
return router |
|||
} |
@ -0,0 +1,14 @@ |
|||
export const Index = async ctx => { |
|||
return await ctx.render("index", { name: "bluescurry" }) |
|||
} |
|||
|
|||
export const Page = (name, data) => async ctx => { |
|||
return await ctx.render(name, data) |
|||
} |
|||
|
|||
import Router from "utils/router.js" |
|||
export function createRoutes() { |
|||
const router = new Router() |
|||
router.get("/", Index) |
|||
return router |
|||
} |
@ -0,0 +1,78 @@ |
|||
import { resolve } from "path" |
|||
import consolidate from "consolidate" |
|||
import send from "../Send" |
|||
import getPaths from "get-paths" |
|||
// import pretty from "pretty"
|
|||
import { logger } from "@/logger" |
|||
|
|||
export default viewsMiddleware |
|||
|
|||
function viewsMiddleware(path, { engineSource = consolidate, extension = "html", options = {}, map } = {}) { |
|||
return function views(ctx, next) { |
|||
if (ctx.render) return next() |
|||
|
|||
ctx.getRender = function (relPath, locals = {}) { |
|||
return getPaths(path, relPath, extension).then(paths => { |
|||
const suffix = paths.ext |
|||
const state = Object.assign(locals, options, ctx.state || {}) |
|||
state.partials = Object.assign({}, options.partials || {}) |
|||
|
|||
if (isHtml(suffix) && !map) { |
|||
return send.getBody(ctx, paths.rel, { root: path }) |
|||
} |
|||
|
|||
const engineName = map && map[suffix] ? map[suffix] : suffix |
|||
const render = engineSource[engineName] |
|||
|
|||
if (!engineName || !render) { |
|||
return Promise.reject(new Error(`Engine not found for the ".${suffix}" file extension`)) |
|||
} |
|||
|
|||
return render(resolve(path, paths.rel), state) |
|||
}) |
|||
} |
|||
|
|||
// 将 render 注入到 context 和 response 对象中
|
|||
ctx.response.render = ctx.render = function (relPath, locals = {}) { |
|||
return getPaths(path, relPath, extension).then(paths => { |
|||
const suffix = paths.ext |
|||
const state = Object.assign(locals, options, ctx.state || {}) |
|||
// deep copy partials
|
|||
state.partials = Object.assign({}, options.partials || {}) |
|||
logger.debug("render `%s` with %j", paths.rel, state) |
|||
ctx.type = "text/html" |
|||
|
|||
// 如果是 html 文件,不编译直接 send 静态文件
|
|||
if (isHtml(suffix) && !map) { |
|||
return send(ctx, paths.rel, { |
|||
root: path, |
|||
}) |
|||
} else { |
|||
const engineName = map && map[suffix] ? map[suffix] : suffix |
|||
|
|||
// 使用 engineSource 配置的渲染引擎 render
|
|||
const render = engineSource[engineName] |
|||
|
|||
if (!engineName || !render) return Promise.reject(new Error(`Engine not found for the ".${suffix}" file extension`)) |
|||
|
|||
return render(resolve(path, paths.rel), state).then(html => { |
|||
// since pug has deprecated `pretty` option
|
|||
// we'll use the `pretty` package in the meanwhile
|
|||
// if (locals.pretty) {
|
|||
// debug("using `pretty` package to beautify HTML")
|
|||
// html = pretty(html)
|
|||
// }
|
|||
ctx.body = html |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// 中间件执行结束
|
|||
return next() |
|||
} |
|||
} |
|||
|
|||
function isHtml(ext) { |
|||
return ext === "html" |
|||
} |
@ -0,0 +1 @@ |
|||
<a href="/page/htmx">#{title || '默认标题'}</a> |
@ -0,0 +1,26 @@ |
|||
extends ./layouts/page.pug |
|||
|
|||
|
|||
block pageRoot |
|||
- var title = '示例页面标题' |
|||
|
|||
block pageContent |
|||
.container.mt-5 |
|||
.row.justify-content-center |
|||
.col-md-8.text-center |
|||
img.rounded-circle.shadow.mb-4(src='https://avatars.githubusercontent.com/u/9919?s=200&v=4', alt='Avatar', width='120', height='120') |
|||
h1.mt-3.mb-1 你的姓名 |
|||
h4.text-muted 你的职位 / 头衔 |
|||
p.lead.mt-3 这里是一段简短的自我介绍,突出你的专业技能、兴趣或座右铭。 |
|||
button(hx-post="/clicked" hx-swap="outerHTML") Click Me |
|||
hr.my-4 |
|||
.d-flex.justify-content-center.gap-4 |
|||
a(href='mailto:your@email.com', target='_blank') |
|||
i.fas.fa-envelope.me-2 |
|||
| 邮箱 |
|||
a(href='https://github.com/your-github', target='_blank') |
|||
i.fab.fa-github.me-2 |
|||
| GitHub |
|||
a(href='https://your-website.com', target='_blank') |
|||
i.fas.fa-globe.me-2 |
|||
| 个人网站 |
@ -0,0 +1,12 @@ |
|||
block root |
|||
doctype html |
|||
html(lang="zh-CN") |
|||
head |
|||
title #{title || '默认标题'} |
|||
meta(charset="utf-8") |
|||
meta(name="viewport" content="width=device-width, initial-scale=1") |
|||
script(src="https://unpkg.com/htmx.org@2.0.4") |
|||
block head |
|||
body |
|||
block content |
|||
block scripts |
@ -0,0 +1,17 @@ |
|||
|
|||
extends ./base.pug |
|||
|
|||
block root |
|||
block pageRoot |
|||
|
|||
block head |
|||
link(href='https://cdn.bootcss.com/twitter-bootstrap/4.1.3/css/bootstrap.min.css' rel='stylesheet') |
|||
script(src='https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js') |
|||
script(src='https://cdn.bootcss.com/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js') |
|||
block pageHead |
|||
|
|||
block content |
|||
block pageContent |
|||
|
|||
block scripts |
|||
block pageScripts |
Loading…
Reference in new issue