23 changed files with 1383 additions and 6141 deletions
File diff suppressed because it is too large
@ -1,136 +0,0 @@ |
|||||
@charset "UTF-8"; |
|
||||
/* 首页样式 */ |
|
||||
.hero-section { |
|
||||
position: relative; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
|
|
||||
.hero-section::before { |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
bottom: 0; |
|
||||
background: url("/images/hero-bg.svg") no-repeat center center; |
|
||||
background-size: cover; |
|
||||
opacity: 0.1; |
|
||||
z-index: 0; |
|
||||
} |
|
||||
|
|
||||
.hero-content { |
|
||||
position: relative; |
|
||||
z-index: 1; |
|
||||
} |
|
||||
|
|
||||
.feature-card { |
|
||||
transition: all 0.3s ease; |
|
||||
} |
|
||||
|
|
||||
.feature-card:hover { |
|
||||
transform: translateY(-5px); |
|
||||
} |
|
||||
|
|
||||
.feature-card .material-symbols-light--article, |
|
||||
.feature-card .material-symbols-light--bookmark, |
|
||||
.feature-card .material-symbols-light--person { |
|
||||
transition: all 0.3s ease; |
|
||||
} |
|
||||
|
|
||||
.feature-card:hover .material-symbols-light--article, |
|
||||
.feature-card:hover .material-symbols-light--bookmark, |
|
||||
.feature-card:hover .material-symbols-light--person { |
|
||||
transform: scale(1.1); |
|
||||
} |
|
||||
|
|
||||
.stats-section { |
|
||||
position: relative; |
|
||||
} |
|
||||
|
|
||||
.stats-section::before { |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
bottom: 0; |
|
||||
background: url("/images/stats-bg.svg") no-repeat center center; |
|
||||
background-size: cover; |
|
||||
opacity: 0.05; |
|
||||
z-index: 0; |
|
||||
} |
|
||||
|
|
||||
.stat-item { |
|
||||
transition: all 0.3s ease; |
|
||||
} |
|
||||
|
|
||||
.stat-item:hover { |
|
||||
transform: scale(1.05); |
|
||||
} |
|
||||
|
|
||||
.user-dashboard { |
|
||||
position: relative; |
|
||||
} |
|
||||
|
|
||||
.user-dashboard::before { |
|
||||
content: ""; |
|
||||
position: absolute; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
bottom: 0; |
|
||||
background: url("/images/dashboard-bg.svg") no-repeat center center; |
|
||||
background-size: cover; |
|
||||
opacity: 0.03; |
|
||||
z-index: 0; |
|
||||
} |
|
||||
|
|
||||
.avatar { |
|
||||
transition: all 0.3s ease; |
|
||||
} |
|
||||
|
|
||||
.avatar:hover { |
|
||||
transform: scale(1.05); |
|
||||
} |
|
||||
|
|
||||
/* 响应式设计 */ |
|
||||
@media (max-width: 768px) { |
|
||||
.hero-section { |
|
||||
padding: 4rem 0; |
|
||||
} |
|
||||
.hero-content h1 { |
|
||||
font-size: 2.5rem; |
|
||||
} |
|
||||
.features-grid { |
|
||||
grid-template-columns: 1fr; |
|
||||
} |
|
||||
.stats-grid { |
|
||||
grid-template-columns: 1fr 1fr; |
|
||||
} |
|
||||
.user-info { |
|
||||
text-align: center; |
|
||||
margin-bottom: 1.5rem; |
|
||||
} |
|
||||
.user-actions { |
|
||||
justify-content: center; |
|
||||
} |
|
||||
} |
|
||||
@media (max-width: 480px) { |
|
||||
.hero-content h1 { |
|
||||
font-size: 2rem; |
|
||||
} |
|
||||
.hero-content p { |
|
||||
font-size: 1rem; |
|
||||
} |
|
||||
.stats-grid { |
|
||||
grid-template-columns: 1fr; |
|
||||
} |
|
||||
.hero-actions { |
|
||||
flex-direction: column; |
|
||||
gap: 1rem; |
|
||||
} |
|
||||
.hero-actions a { |
|
||||
width: 100%; |
|
||||
text-align: center; |
|
||||
} |
|
||||
} |
|
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,25 @@ |
|||||
|
import { R } from "utils/helper.js" |
||||
|
import Router from "utils/router.js" |
||||
|
import BaseController from "@/base/BaseController.js" |
||||
|
|
||||
|
class AuthController extends BaseController { |
||||
|
constructor() { |
||||
|
super() |
||||
|
} |
||||
|
|
||||
|
async loginPost(ctx) { |
||||
|
return this.success(ctx, null, "登录成功") |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 路由注册 |
||||
|
*/ |
||||
|
static createRoutes() { |
||||
|
const controller = new AuthController() |
||||
|
const router = new Router({ prefix: "/api/auth", auth: "try" }) |
||||
|
router.post("/login", controller.handleRequest(controller.loginPost)) |
||||
|
return router |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default AuthController |
||||
@ -0,0 +1,35 @@ |
|||||
|
import Router from "utils/router.js" |
||||
|
import { logger } from "@/logger.js" |
||||
|
import BaseController from "@/base/BaseController.js" |
||||
|
|
||||
|
export default class AuthController extends BaseController { |
||||
|
constructor() { |
||||
|
super() |
||||
|
} |
||||
|
|
||||
|
// 首页
|
||||
|
async loginGet(ctx) { |
||||
|
return this.render(ctx, "page/login/index", {}) |
||||
|
} |
||||
|
|
||||
|
async loginPost(ctx) { |
||||
|
console.log(ctx.request.body); |
||||
|
return this.success(ctx, null, "登录成功") |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建基础页面相关路由 |
||||
|
* @returns {Router} 路由实例 |
||||
|
*/ |
||||
|
static createRoutes() { |
||||
|
const controller = new AuthController() |
||||
|
const router = new Router({ auth: false }) |
||||
|
|
||||
|
// 首页
|
||||
|
router.get("", controller.handleRequest(controller.loginGet)) |
||||
|
router.get("/login", controller.handleRequest(controller.loginGet)) |
||||
|
router.post("/login", controller.handleRequest(controller.loginPost)) |
||||
|
|
||||
|
return router |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
import UserModel from "../db/models/UserModel.js" |
||||
|
import CommonError from "utils/error/CommonError.js" |
||||
|
import { comparePassword } from "utils/bcrypt.js" |
||||
|
|
||||
|
/** |
||||
|
* 认证服务类 |
||||
|
* 提供认证相关的业务逻辑 |
||||
|
*/ |
||||
|
class AuthService { |
||||
|
// 注册新用户
|
||||
|
static async register(data) { |
||||
|
try { |
||||
|
if (!data.username || !data.password) { |
||||
|
throw new CommonError("用户名和密码不能为空") |
||||
|
} |
||||
|
|
||||
|
// 检查用户名是否已存在
|
||||
|
const existUser = await UserModel.findByUsername(data.username) |
||||
|
if (existUser) { |
||||
|
throw new CommonError(`用户名${data.username}已存在`) |
||||
|
} |
||||
|
|
||||
|
// 检查邮箱是否已存在
|
||||
|
if (data.email) { |
||||
|
const existEmail = await UserModel.findByEmail(data.email) |
||||
|
if (existEmail) { |
||||
|
throw new CommonError(`邮箱${data.email}已被使用`) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 密码加密
|
||||
|
const hashed = await hashPassword(data.password) |
||||
|
|
||||
|
const user = await UserModel.create({ ...data, password: hashed }) |
||||
|
|
||||
|
// 返回脱敏信息
|
||||
|
const { password, ...userInfo } = Array.isArray(user) ? user[0] : user |
||||
|
return userInfo |
||||
|
} catch (error) { |
||||
|
if (error instanceof CommonError) throw error |
||||
|
throw new CommonError(`注册失败: ${error.message}`) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 登录
|
||||
|
static async login({ username, email, password }) { |
||||
|
try { |
||||
|
if (!password) { |
||||
|
throw new CommonError("密码不能为空") |
||||
|
} |
||||
|
|
||||
|
if (!username && !email) { |
||||
|
throw new CommonError("用户名或邮箱不能为空") |
||||
|
} |
||||
|
|
||||
|
let user |
||||
|
if (username) { |
||||
|
user = await UserModel.findByUsername(username) |
||||
|
} else if (email) { |
||||
|
user = await UserModel.findByEmail(email) |
||||
|
} |
||||
|
|
||||
|
if (!user) { |
||||
|
throw new CommonError("用户不存在") |
||||
|
} |
||||
|
|
||||
|
// 校验密码
|
||||
|
const ok = await comparePassword(password, user.password) |
||||
|
if (!ok) { |
||||
|
throw new CommonError("密码错误") |
||||
|
} |
||||
|
|
||||
|
// 生成token
|
||||
|
const token = jwt.sign({ id: user.id, username: user.username }, JWT_SECRET, { expiresIn: "2h" }) |
||||
|
|
||||
|
// 返回token和用户信息
|
||||
|
const { password: pwd, ...userInfo } = user |
||||
|
return { token, user: userInfo } |
||||
|
} catch (error) { |
||||
|
if (error instanceof CommonError) throw error |
||||
|
throw new CommonError(`登录失败: ${error.message}`) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
@mixin chinese() { |
||||
|
&.chinese { |
||||
|
text-indent: 1.5em; |
||||
|
font-weight: 300; |
||||
|
h1, |
||||
|
h2, |
||||
|
h3, |
||||
|
h4, |
||||
|
h5, |
||||
|
h6, |
||||
|
ol, |
||||
|
ul, |
||||
|
blockquote, |
||||
|
details, |
||||
|
summary, |
||||
|
pre, |
||||
|
.tabs { |
||||
|
text-indent: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
footer.footer.shadow.mt-5 |
||||
|
.footer-panel(class="bg-white border-t border-gray-200") |
||||
|
.footer-content.container(class="pt-12 pb-6") |
||||
|
.footer-main(class="grid grid-cols-1 md:grid-cols-4 gap-8 mb-8") |
||||
|
.footer-section |
||||
|
h3.footer-title(class="text-lg font-semibold text-gray-900 mb-4") #{siteConfig.site_title} |
||||
|
p.footer-desc(class="text-gray-600 text-sm leading-relaxed") 明月照佳人,用真心对待世界。<br>岁月催人老,用真情对待自己。 |
||||
|
|
||||
|
.footer-section |
||||
|
h3.footer-title(class="text-lg font-semibold text-gray-900 mb-4") 快速链接 |
||||
|
ul.footer-links(class="space-y-3") |
||||
|
li |
||||
|
a(href="/" class="text-gray-600 hover:text-blue-600 transition-colors duration-200 text-sm") 首页 |
||||
|
li |
||||
|
a(href="/about" class="text-gray-600 hover:text-blue-600 transition-colors duration-200 text-sm") 关于我们 |
||||
|
li |
||||
|
a(href="/contact" class="text-gray-600 hover:text-blue-600 transition-colors duration-200 text-sm") 联系我们 |
||||
|
li |
||||
|
a(href="/help" class="text-gray-600 hover:text-blue-600 transition-colors duration-200 text-sm") 帮助中心 |
||||
|
|
||||
|
.footer-section |
||||
|
h3.footer-title(class="text-lg font-semibold text-gray-900 mb-4") 服务支持 |
||||
|
ul.footer-links(class="space-y-3") |
||||
|
li |
||||
|
a(href="/terms" class="text-gray-600 hover:text-blue-600 transition-colors duration-200 text-sm") 服务条款 |
||||
|
li |
||||
|
a(href="/privacy" class="text-gray-600 hover:text-blue-600 transition-colors duration-200 text-sm") 隐私政策 |
||||
|
li |
||||
|
a(href="/faq" class="text-gray-600 hover:text-blue-600 transition-colors duration-200 text-sm") 常见问题 |
||||
|
li |
||||
|
a(href="/feedback" class="text-gray-600 hover:text-blue-600 transition-colors duration-200 text-sm") 意见反馈 |
||||
|
|
||||
|
.footer-section |
||||
|
h3.footer-title(class="text-lg font-semibold text-gray-900 mb-4") 关注我 |
||||
|
.social-links(class="flex space-x-4 flex-wrap") |
||||
|
a(href="#" class="social-link p-2 rounded-full bg-gray-100 hover:bg-blue-100 transition-colors duration-200" title="微信") |
||||
|
span.streamline-ultimate-color--wechat-logo |
||||
|
a(href="#" class="social-link p-2 rounded-full bg-gray-100 hover:bg-blue-100 transition-colors duration-200" title="QQ") |
||||
|
span.cib--tencent-qq |
||||
|
a(href="#" class="social-link p-2 rounded-full bg-gray-100 hover:bg-gray-200 transition-colors duration-200" title="GitHub") |
||||
|
span.ri--github-fill |
||||
|
a(href="https://blog.xieyaxin.top" target="_blank" class="social-link p-2 rounded-full bg-gray-100 hover:bg-gray-200 transition-colors duration-200" title="GitHub") |
||||
|
span.icomoon-free--blog |
||||
|
|
||||
|
.footer-bottom(class="border-t border-gray-200 pt-6") |
||||
|
.footer-bottom-content(class="flex flex-col md:flex-row justify-between items-center") |
||||
|
.copyright(class="text-gray-500 text-sm mb-4 md:mb-0") |
||||
|
| © 2023-#{new Date().getFullYear()} #{siteConfig.site_author}. 保留所有权利。 |
||||
|
.footer-actions(class="flex items-center space-x-6") |
||||
|
a(href="/sitemap" class="text-gray-500 hover:text-blue-600 transition-colors duration-200 text-sm") 网站地图 |
||||
|
a(href="/rss" class="text-gray-500 hover:text-blue-600 transition-colors duration-200 text-sm") RSS订阅 |
||||
@ -0,0 +1,11 @@ |
|||||
|
:scss |
||||
|
@import "./style.scss"; |
||||
|
@import "./markdown-reset.scss"; |
||||
|
@import "./markdown-green.scss"; |
||||
|
|
||||
|
.markdown-body.green |
||||
|
:markdown-it(linkify langPrefix='highlight-') |
||||
|
# 关于 |
||||
|
这是一个关于页面,欢迎访问。 |
||||
|
|
||||
|
> 世上本无事,庸人自扰之。 |
||||
@ -1,47 +1,21 @@ |
|||||
@mixin chinese() { |
|
||||
&.chinese { |
|
||||
text-indent: 1.5em; |
|
||||
font-weight: 300; |
|
||||
h1, |
|
||||
h2, |
|
||||
h3, |
|
||||
h4, |
|
||||
h5, |
|
||||
h6, |
|
||||
ol, |
|
||||
ul, |
|
||||
blockquote, |
|
||||
details, |
|
||||
summary, |
|
||||
pre, |
|
||||
.tabs { |
|
||||
text-indent: 0; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
// ::selection { |
|
||||
// background-color: #1abc9c; |
|
||||
// color: #fff; |
|
||||
// } |
|
||||
|
|
||||
// pre ::selection { |
.markdown-body.green { |
||||
// color: inherit; |
|
||||
// } |
|
||||
|
|
||||
.markdown-body { |
|
||||
--color-base: #ef4444; |
--color-base: #ef4444; |
||||
--markdown-bg: white; |
--color-base-text: #fff; |
||||
--color-bg: #ff47479c; |
--color-bg: #ff47479c; |
||||
--color-light: #ef44441a; |
--color-light: #ef44441a; |
||||
--color-extra: rgba(239, 68, 68, 0.3); |
--color-extra: rgba(239, 68, 68, 0.3); |
||||
--color-more: rgba(239, 68, 68, 0.4); |
--color-more: rgba(239, 68, 68, 0.4); |
||||
} |
|
||||
|
--prism-background: #eeeeee; |
||||
|
--prism-font-size: 14px; |
||||
|
--prism-font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; |
||||
|
|
||||
.markdown-body.green { |
::selection { |
||||
background-color: var(--markdown-bg); |
background-color: var(--color-base); |
||||
@apply p-3 lg:p-6; |
color: var(--color-base-text); |
||||
|
} |
||||
|
|
||||
strong { |
strong { |
||||
&::before { |
&::before { |
||||
@ -0,0 +1,26 @@ |
|||||
|
nav.navbar(class="relative") |
||||
|
.placeholder.mb-5(class="h-[45px] w-full opacity-0") |
||||
|
.fixed-container(class="shadow fixed bg-white h-[45px] top-0 left-0 right-0 z-10") |
||||
|
.container.clearfix(class="h-full") |
||||
|
.navbar-brand |
||||
|
a(href="/" class="text-[20px]") #{siteConfig.site_title} |
||||
|
.left.menu.desktop-only |
||||
|
a.menu-item( |
||||
|
href="/articles" |
||||
|
class=(currentPath === '/articles' || currentPath === '/articles/' |
||||
|
? 'text-blue-600 font-bold border-b-2 border-blue-600' |
||||
|
: 'text-gray-700 hover:text-blue-600 hover:border-b-2 hover:border-blue-400' |
||||
|
) |
||||
|
) 所有文章 |
||||
|
if !user |
||||
|
.right.menu.desktop-only |
||||
|
a.menu-item(href="/login") 登录 |
||||
|
a.menu-item(href="/register") 注册 |
||||
|
else |
||||
|
.right.menu.desktop-only |
||||
|
a.menu-item(href="/profile") |
||||
|
span 欢迎您, |
||||
|
span.font-semibold #{user.name || user.username} |
||||
|
a.menu-item(href="/notice") |
||||
|
.fe--notice-active |
||||
|
a.menu-item(hx-post="/logout") 退出 |
||||
@ -1,22 +1,6 @@ |
|||||
extends /layouts/empty.pug |
extends /layouts/empty.pug |
||||
|
|
||||
block pageHead |
block pageHead |
||||
:scss |
|
||||
@use "./_global.scss"; |
|
||||
@use "./style.scss"; |
|
||||
@use "./markdown-reset.scss"; |
|
||||
@use "./markdown-green.scss"; |
|
||||
|
|
||||
|
|
||||
block pageContent |
block pageContent |
||||
.markdown-body |
include /htmx/markdown/index.pug |
||||
:markdown-it(linkify langPrefix='highlight-') |
|
||||
# Markdown |
|
||||
|
|
||||
Markdown document with http://links.com and |
|
||||
|
|
||||
```js |
|
||||
var codeBlocks; |
|
||||
``` |
|
||||
|
|
||||
> blockquote |
|
||||
@ -0,0 +1,9 @@ |
|||||
|
extends /layouts/empty.pug |
||||
|
|
||||
|
block pageHead |
||||
|
|
||||
|
block pageContent |
||||
|
form(action="/login" method="post") |
||||
|
input(type="text" name="username" placeholder="用户名") |
||||
|
input(type="password" name="password" placeholder="密码") |
||||
|
button(type="submit") 登录 |
||||
Loading…
Reference in new issue